# AbstractMethodTestDescriptor.java

    /*
    ** Module   : AbstractMethodTestDescriptor.java
    ** Abstract : Test descriptor for a Java method.
    **
    ** Copyright (c) 2023-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ----------------Description----------------------
    ** 001 VVT 20230318 Created initial version.
    ** 002 VVT 20230412 OEUnit support added. See #6237.
    ** 003 VVT 20230512 Fixed error propagation (See #3827-350) and source formatting.
    **                  getSource() implementaion added. See #3827-360, item 4.
    **                  Tags support added to FWD engine (FWD extension).
    ** 004 VVT 20240321 Javadocs updated for getTestObject(). See #8406.
    **                  Missing parameter description added.
    ** 005 VVT 20240408 Commented code removed.
    ** 006 VVT 20240718 SuppressWarnings added.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */
    package com.goldencode.p2j.testengine;

    import static com.goldencode.p2j.util.BlockManager.*;

    import java.io.*;
    import java.lang.reflect.*;
    import java.util.*;

    import org.junit.platform.engine.*;
    import org.junit.platform.engine.support.descriptor.*;

    import com.goldencode.p2j.oo.oeunit.data.*;
    import com.goldencode.p2j.util.*;

    /**
     * Test descriptor for a Java method.
     */
    public abstract class AbstractMethodTestDescriptor
    extends AbstractFWDTestDescriptor
    {
       /**
        * Methods to execute after the main method, if any.
        */
       List<Method> afterEach;

       /**
        * Methods to execute before the main method, if any.
        */
       List<Method> beforeEach;

       /**
        * The Java class to create instances of.
        */
       Class<?> clazz;

       /**
        * This field, if not empty, contains the name of legacy exception
        * class expected to be thrown by the test.
        *
        * This field must never be null.
        */
       String expectedException;

       /**
        * Optional fixture method
        */
       transient Method fixture;

       /**
        * The test method
        */
       Method method;

       /**
        * Optional legacy method invocation parameters, used
        * by test methods with data providers, or {@code null} for no parameters
        */
       transient CallParameter[] parameterList;

       /**
        * Default constructor. Required for serialization only. Not to be used in applications.
        */
       protected AbstractMethodTestDescriptor()
       {
          super(null, null, null);
       }

       /**
        * Constructor.
        * <p>
        * The unique ID and display name for the descriptor are calculated based on
        * the parent descriptor ID and the method name.
        *
        * @param parent
        *        the parent descriptor
        * @param clazz
        *        the Java class to create instances
        * @param execute
        *        the method to execute
        * @param parameterList
        *        the list of method call parameters
        * @param fixture
        *        if not {@code null}, the method of the same class used as a fixture
        *        for other test method(s)
        * @param beforeEach
        *        methods to execute before the execute method
        * @param afterEach
        *        methods to execute after the execute method
        * @param expectedException
        *        if not {@code null}, the expected exception legacy class name
        */
       protected AbstractMethodTestDescriptor(final AbstractClassTestDescriptor parent,
                                              final Class<?> clazz,
                                              final Method execute,
                                              final CallParameter[] parameterList,
                                              final Method fixture,
                                              final List<Method> beforeEach,
                                              final List<Method> afterEach,
                                              final String expectedException)
       {
          this(parent.getUniqueId().append("method", execute.getName()), execute.getName(), clazz,
               execute, parameterList, fixture, beforeEach, afterEach, expectedException);
       }

       /**
        * Constructor.
        *
        * @param id
        *        thr ID for this test descriptor
        * @param displayName
        *        the display name
        * @param clazz
        *        the Java class to create instances
        * @param execute
        *        the method to execute
        * @param parameterList TODO
        * @param fixture
        *        if not {@code null}, the method of the same class used as a fixture
        *        for other test method(s)
        * @param beforeEach
        *        methods to execute before the execute method
        * @param afterEach
        *        methods to execute after the execute method
        * @param expectedException
        *        if not {@code null}, the expected exception legacy class name
        */
       protected AbstractMethodTestDescriptor(final UniqueId id,
                                              final String displayName,
                                              final Class<?> clazz,
                                              final Method execute,
                                              final CallParameter[] parameterList,
                                              final Method fixture,
                                              final List<Method> beforeEach,
                                              final List<Method> afterEach,
                                              final String expectedException)
       {
          super(id, displayName, getTags(execute));

          this.clazz = clazz;
          this.method = execute;
          this.beforeEach = beforeEach;
          this.afterEach = afterEach;
          this.expectedException = expectedException;
          this.fixture = fixture;
          this.parameterList = parameterList;
       }

       /**
        * Execute the <em>after</em> behavior of this node implementation.
        * <p>
        * This method will be called once <em>after</em> {@linkplain #executeImpl() execution}
        * of this node.
        */
       @Override
       public void afterImpl()
       throws ErrorConditionException
       {
          callLFMethodsImpl(afterEach);
       }

       /**
        * Execute the <em>before</em> behavior of this node implementation.
        * <p>
        * This method will be called once <em>before</em> {@linkplain #executeImpl() execution}
        * of this node.
        */
       @Override
       public void beforeImpl()
       throws ErrorConditionException
       {
          callLFMethodsImpl(beforeEach);
       }

       /**
        * Execute the <em>behavior</em> of this node.
        * <p>
        * Containers typically do not implement this method since the
        * {@link HierarchicalTestEngine} handles execution of their children.
        * <p>
        * The supplied {@code dynamicTestExecutor} may be used to submit
        * additional dynamic tests for immediate execution.
        *
        * @param  context
        *         the context to execute in
        * @param  dynamicTestExecutor
        *         the executor to submit dynamic tests to
        *
        * @return the new context to be used for children of this node and for the
        *         <em>after</em> behavior of the parent of this node, if any
        */
       @Override
       public FWDEngineExecutionContext execute(final FWDEngineExecutionContext context,
                                                DynamicTestExecutor dynamicTestExecutor)
       throws ErrorConditionException
       {
          context.unitTestSupport.execute(getUniqueId());
          return context;
       }

       /**
        * Execute the implementation <em>behavior</em> of this node.
        */
       @Override
       public void executeImpl()
       throws ErrorConditionException
       {
          if (fixture == null)
          {
             executeTestMethod();
             return;
          }

          final String label = "oeunitFixtureTransaction";
          doBlock(TransactionType.FULL, label, new OnPhrase[] {
                   new OnPhrase(Condition.ERROR, Action.THROW, label)
          }, new Block((Body) () -> {

             // 1. Obtain the fixture object
             final BaseDataType result = callMethodImpl(fixture, parameterList);
             // FIXME: at discovery time add test if the method return value is an instance of fixture
             final object<? extends Fixture_> fixtureObject = (object<? extends Fixture_>) result;

             // 2. Apply the feature and test methods
             try
             {
                fixtureObject.ref().createData();
                executeTestMethod();
             }
             finally
             {
                ObjectOps.delete(fixtureObject);
             }

             try
             {
                undoReturnNormal(label);
             }
             catch (@SuppressWarnings("unused") final ReturnUnwindException e)
             {
                // ignore
             }
          }));
       }

       /**
        * Get the {@linkplain TestSource source} of the test or container described
        * by this descriptor, if available.
        *
        * @see TestSource
        */
       @Override
       public Optional<TestSource> getSource()
       {
          return Optional.of(MethodSource.from(method));
       }

       /**
        * The object implements the readExternal method to restore its
        * contents by calling the methods of DataInput for primitive
        * types and readObject for objects, strings and arrays.  The
        * readExternal method must read the values in the same sequence
        * and with the same types as were written by writeExternal.
        *
        * @param     in
        *            the stream to read data from in order to restore the object
        *
        * @exception IOException if I/O errors occur
        * @exception ClassNotFoundException If the class for an object being
        *            restored cannot be found.
        */
       @Override
       public void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          super.readExternal(in);

          clazz = (Class<?>) in.readObject();
          method = SerializationSupport.readMethod(in);
          beforeEach = SerializationSupport.readMethods(in);
          afterEach = SerializationSupport.readMethods(in);
          expectedException = in.readUTF();
       }

       /**
        * The object implements the writeExternal method to save its contents
        * by calling the methods of DataOutput for its primitive values or
        * calling the writeObject method of ObjectOutput for objects, strings,
        * and arrays.
        *
        * @serialData Overriding methods should use this tag to describe
        *             the data layout of this Externalizable object.
        *             List the sequence of element types and, if possible,
        *             relate the element to a public/protected field and/or
        *             method of this Externalizable class.
        *
        * @param      out the stream to write the object to
        *
        * @exception  IOException Includes any I/O exceptions that may occur
        */
       @Override
       public void writeExternal(ObjectOutput out)
       throws IOException
       {
          super.writeExternal(out);

          out.writeObject(clazz);
          SerializationSupport.writeMethod(method, out);
          SerializationSupport.writeMethods(beforeEach, out);
          SerializationSupport.writeMethods(afterEach, out);
          out.writeUTF(expectedException);
       }

       /**
        * Get test object. The test object is either a legacy class instance if tests are implemented
        * as legacy class methods or a persistent procedure instance it tests are implemented as
        * internal procedures.
        * <p>
        * The value should be {@code null} if the test method is a static legacy class method.
        *
        * @return the test object or {@code null}
        */
       @Override
       protected Object getTestObject()
       {
          // The text object is stored by the class test descriptor
          return ((AbstractFWDTestDescriptor) getParent().get()).getTestObject();
       }

       /**
        * Execute the test method.
        *
        * @throws ErrorConditionException
        *         thrown by the method executed
        * @throws LegacyErrorException
        *         legacy errors are no caught by this method
        */
       private void executeTestMethod()
       throws ErrorConditionException,
              LegacyErrorException
       {
          callMethodImpl(method, parameterList);
       }

    }