/** * Copyright 2011-2017 Asakusa Framework Team. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.asakusafw.testdriver.tools.runner; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.UnaryOperator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.asakusafw.runtime.directio.DataFormat; import com.asakusafw.testdriver.DriverElementBase; import com.asakusafw.testdriver.TestDriverContext; import com.asakusafw.testdriver.core.DataModelDefinition; import com.asakusafw.testdriver.core.DataModelSinkFactory; import com.asakusafw.testdriver.core.DataModelSource; import com.asakusafw.testdriver.core.DataModelSourceFactory; import com.asakusafw.testdriver.core.Difference; import com.asakusafw.testdriver.core.ModelTester; import com.asakusafw.testdriver.core.ModelTransformer; import com.asakusafw.testdriver.core.TestContext; import com.asakusafw.testdriver.core.TestModerator; import com.asakusafw.testdriver.core.TestRule; import com.asakusafw.testdriver.core.TestToolRepository; import com.asakusafw.testdriver.core.VerifierFactory; import com.asakusafw.testdriver.core.VerifyContext; import com.asakusafw.testdriver.core.VerifyRuleFactory; import com.asakusafw.vocabulary.external.ExporterDescription; import com.asakusafw.vocabulary.external.ImporterDescription; /** * Testing tools for Asakusa batch applications. * @see BatchTestRunner * @since 0.7.3 * @version 0.9.1 */ public class BatchTestTool extends DriverElementBase implements TestContext { private static final Logger LOG = LoggerFactory.getLogger(BatchTestTool.class); private final ClassLoader classLoader; private final Map<String, String> batchArguments; private final Map<String, String> environmentVariables; private final Class<?> callerClass; private final TestToolRepository tools; /** * Creates a new instance. * @param callerClass the caller class (for detecting testing resources) */ public BatchTestTool(Class<?> callerClass) { this.callerClass = callerClass; this.classLoader = callerClass.getClassLoader(); this.batchArguments = new LinkedHashMap<>(); this.environmentVariables = new LinkedHashMap<>(System.getenv()); this.tools = new TestToolRepository(classLoader); } /** * Creates a new instance. * @param callerClass the caller class (for detecting testing resources) * @param context the current testing context */ public BatchTestTool(Class<?> callerClass, TestContext context) { this(callerClass, context, new TestToolRepository(context.getClassLoader())); } /** * Creates a new instance. * @param context the current context */ public BatchTestTool(TestDriverContext context) { this(context.getCallerClass(), context, context.getRepository()); } /** * Creates a new instance. * @param callerClass the caller class (for detecting testing resources) * @param context the current testing context * @param tools the testing tools */ protected BatchTestTool(Class<?> callerClass, TestContext context, TestToolRepository tools) { this.classLoader = context.getClassLoader(); this.batchArguments = new LinkedHashMap<>(context.getArguments()); this.environmentVariables = new LinkedHashMap<>(context.getEnvironmentVariables()); this.callerClass = callerClass; this.tools = new TestToolRepository(classLoader); } @Override public ClassLoader getClassLoader() { return classLoader; } @Override public Map<String, String> getArguments() { return batchArguments; } @Override public Map<String, String> getEnvironmentVariables() { return environmentVariables; } @Override protected final Class<?> getCallerClass() { return callerClass; } @Override protected final TestToolRepository getTestTools() { return tools; } /** * Truncates jobflow input. * @param description the target importer description * @throws IOException if failed to truncate the input */ public void truncate(ImporterDescription description) throws IOException { LOG.info(MessageFormat.format( Messages.getString("BatchTestTool.infoCleaningInput"), //$NON-NLS-1$ description.getClass().getName())); TestModerator moderator = new TestModerator(getTestTools(), this); moderator.truncate(description); } /** * Truncates jobflow output. * @param description the target exporter description * @throws IOException if failed to truncate the output */ public void truncate(ExporterDescription description) throws IOException { LOG.info(MessageFormat.format( Messages.getString("BatchTestTool.infoCleaningOutput"), //$NON-NLS-1$ description.getClass().getName())); TestModerator moderator = new TestModerator(getTestTools(), this); moderator.truncate(description); } /** * Prepares jobflow input. * @param description the target importer description * @param dataPath the data URI * @throws IOException if failed to prepare the output */ public void prepare(ImporterDescription description, String dataPath) throws IOException { try { DataModelSourceFactory source = resolveSource(dataPath); prepare(description, source); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(MessageFormat.format( Messages.getString("BatchTestTool.errorFailedToPrepareInput"), //$NON-NLS-1$ description.getClass().getName()), e); } } /** * Prepares jobflow input. * @param description the importer description * @param source the data model source * @throws IOException if failed to prepare the input */ public void prepare(ImporterDescription description, DataModelSourceFactory source) throws IOException { TestModerator moderator = new TestModerator(getTestTools(), this); moderator.prepare(description.getModelType(), description, source); } /** * Collects jobflow output. * @param description the target exporter description * @param outputPath the output URI * @throws IOException if failed to collect the output * @since 0.9.1 */ public void collect(ExporterDescription description, String outputPath) throws IOException { try { DataModelSinkFactory sink = resolveSink(outputPath); collect(description, sink); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(MessageFormat.format( Messages.getString("BatchTestTool.errorFailedToCollectOutput"), //$NON-NLS-1$ description.getClass().getName()), e); } } /** * Collects jobflow output. * @param description the exporter description * @param sink the output sink * @throws IOException if failed to collect the output * @since 0.9.1 */ public void collect(ExporterDescription description, DataModelSinkFactory sink) throws IOException { TestModerator moderator = new TestModerator(getTestTools(), this); moderator.save(description.getModelType(), description, sink); } /** * Verifies jobflow output. * @param description the target exporter description * @param expectedPath the expected data URI * @param verifyRulePath the verification rule URI * @param extraRules extra verification rules * @return verify differences * @throws IOException if failed to verify the output */ public List<Difference> verify( ExporterDescription description, String expectedPath, String verifyRulePath, ModelTester<?>... extraRules) throws IOException { return verify(description, expectedPath, verifyRulePath, null, extraRules); } /** * Verifies jobflow output. * @param description the target exporter description * @param expectedPath the expected data URI * @param verifyRulePath the verification rule URI * @param transformer the data model transformer * @param extraRules extra verification rules * @return verify differences * @throws IOException if failed to verify the output */ public List<Difference> verify( ExporterDescription description, String expectedPath, String verifyRulePath, ModelTransformer<?> transformer, ModelTester<?>... extraRules) throws IOException { try { VerifierFactory verifier = toVerifierFactory( description.getModelType(), expectedPath, verifyRulePath, transformer, extraRules); return verify(description, verifier); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(MessageFormat.format( Messages.getString("BatchTestTool.errorFailedToPrepareOutput"), //$NON-NLS-1$ description.getClass().getName()), e); } } @SuppressWarnings("unchecked") private <T> VerifierFactory toVerifierFactory( Class<T> dataType, String expectedPath, String verifyRulePath, ModelTransformer<?> transformer, ModelTester<?>... extraRules) throws URISyntaxException, IOException { DataModelDefinition<T> definition = getTestTools().toDataModelDefinition(dataType); DataModelSourceFactory source = resolveSource(expectedPath); List<TestRule> fragments = new ArrayList<>(); for (ModelTester<?> tester : extraRules) { TestRule rule = getTestTools().toVerifyRuleFragment(definition, (ModelTester<? super T>) tester); fragments.add(rule); } VerifyRuleFactory rules = getTestTools().getVerifyRuleFactory(toUri(verifyRulePath), fragments); VerifierFactory result = getTestTools().toVerifierFactory(source, rules); if (transformer != null) { UnaryOperator<DataModelSource> filter = toDataModelSourceFilter(definition, (ModelTransformer<? super T>) transformer); result = toVerifierFactory(result, filter); } return result; } /** * Verifies jobflow output. * @param description the exporter description * @param verifier the verifier * @return verify differences * @throws IOException if failed to verify the output */ public List<Difference> verify(ExporterDescription description, VerifierFactory verifier) throws IOException { TestModerator moderator = new TestModerator(getTestTools(), this); return moderator.inspect( description.getModelType(), description, new VerifyContext(this), verifier); } private DataModelSourceFactory resolveSource(String path) throws URISyntaxException, IOException { URI uri = toUri(path); return DirectIoInfo.parse(getTestTools(), classLoader, uri) .map(this::toSource) .orElseGet(() -> getTestTools().getDataModelSourceFactory(uri)); } private <T> DataModelSourceFactory toSource(DirectIoInfo<T> info) { File file = new File(info.path); if (file.exists()) { return toDataModelSourceFactory(info.definition, info.formatClass, file); } else { return toDataModelSourceFactory(info.definition, info.formatClass, info.path); } } private DataModelSinkFactory resolveSink(String path) throws IOException { URI uri = toOutputUri(path); return DirectIoInfo.parse(getTestTools(), classLoader, uri) .map(info -> toDataModelSinkFactory(info.definition, info.formatClass, new File(info.path))) .orElseGet(() -> getTestTools().getDataModelSinkFactory(uri)); } private static class DirectIoInfo<T> { private static final String SCHEME_DIRECTIO = "directio"; //$NON-NLS-1$ final DataModelDefinition<T> definition; final Class<? extends DataFormat<T>> formatClass; final String path; DirectIoInfo(DataModelDefinition<T> definition, Class<? extends DataFormat<T>> formatClass, String path) { this.definition = definition; this.formatClass = formatClass; this.path = path; } static <T> Optional<DirectIoInfo<T>> parse( TestToolRepository tools, ClassLoader classLoader, URI uri) throws IOException { if (Objects.equals(uri.getScheme(), SCHEME_DIRECTIO) == false) { return Optional.empty(); } String body = uri.getSchemeSpecificPart(); String[] elements = body.split(":", 2); //$NON-NLS-1$ if (elements.length != 2) { return Optional.empty(); } String className = elements[0]; String dataPath = elements[1]; try { @SuppressWarnings("unchecked") Class<? extends DataFormat<T>> formatClass = (Class<? extends DataFormat<T>>) Class.forName(className, false, classLoader); DataFormat<T> format = formatClass.newInstance(); DataModelDefinition<T> definition = tools.toDataModelDefinition(format.getSupportedType()); return Optional.of(new DirectIoInfo<>(definition, formatClass, dataPath)); } catch (ClassCastException | ReflectiveOperationException e) { throw new IOException(MessageFormat.format( Messages.getString("BatchTestTool.errorInvalidDataFormatClass"), //$NON-NLS-1$ className), e); } } } }