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