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);
   }

}