All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.thymeleaf.testing.templateengine.engine.TestExecutor Maven / Gradle / Ivy

There is a newer version: 3.1.0.M2
Show newest version
/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   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 org.thymeleaf.testing.templateengine.engine;

import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.thymeleaf.IThrottledTemplateProcessor;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.IContext;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.testing.templateengine.context.IProcessingContextBuilder;
import org.thymeleaf.testing.templateengine.context.web.WebProcessingContextBuilder;
import org.thymeleaf.testing.templateengine.engine.cache.TestCacheManager;
import org.thymeleaf.testing.templateengine.engine.resolver.TestEngineMessageResolver;
import org.thymeleaf.testing.templateengine.engine.resolver.TestEngineTemplateResolver;
import org.thymeleaf.testing.templateengine.exception.TestEngineExecutionException;
import org.thymeleaf.testing.templateengine.report.ConsoleTestReporter;
import org.thymeleaf.testing.templateengine.report.ITestReporter;
import org.thymeleaf.testing.templateengine.resolver.ITestableResolver;
import org.thymeleaf.testing.templateengine.standard.resolver.StandardTestableResolver;
import org.thymeleaf.testing.templateengine.testable.ITest;
import org.thymeleaf.testing.templateengine.testable.ITestIterator;
import org.thymeleaf.testing.templateengine.testable.ITestParallelizer;
import org.thymeleaf.testing.templateengine.testable.ITestResult;
import org.thymeleaf.testing.templateengine.testable.ITestSequence;
import org.thymeleaf.testing.templateengine.testable.ITestable;
import org.thymeleaf.util.Validate;





public final class TestExecutor {

    public enum ThrottleType { CHARS, BYTES }

    private ITestableResolver testableResolver = new StandardTestableResolver();
    private IProcessingContextBuilder processingContextBuilder = new WebProcessingContextBuilder(); 
    private List dialects = Collections.singletonList((IDialect)new StandardDialect());
    private int throttleStep = Integer.MAX_VALUE;
    private ThrottleType throttleType = ThrottleType.CHARS;
    protected ITestReporter reporter = new ConsoleTestReporter();
    
    
    private static ThreadLocal threadExecutionId = new ThreadLocal();
    private static ThreadLocal threadTestName = new ThreadLocal();
    private static ThreadLocal threadTest = new ThreadLocal();
    
    
    
    public static String getThreadExecutionId() {
        return threadExecutionId.get();
    }
    
    public static String getThreadTestName() {
        return threadTestName.get();
    }
    
    public static ITest getThreadTest() {
        return threadTest.get();
    }
    
    // protected in order to be accessed from parallelizer threads
    protected static void setThreadExecutionId(final String executionId) {
        threadExecutionId.set(executionId);
    }
    
    private static void setThreadTestName(final String testName) {
        threadTestName.set(testName);
    }
    
    private static void setThreadTest(final ITest test) {
        threadTest.set(test);
    }
    
    
    
    
    public TestExecutor() {
        super();
    }

    
    
    
    public ITestableResolver getTestableResolver() {
        return this.testableResolver;
    }

    public void setTestableResolver(final ITestableResolver testableResolver) {
        this.testableResolver = testableResolver;
    }
    

    
    
    public IProcessingContextBuilder getProcessingContextBuilder() {
        return this.processingContextBuilder;
    }

    public void setProcessingContextBuilder(final IProcessingContextBuilder processingContextBuilder) {
        Validate.notNull(processingContextBuilder, "Processing Context Builder cannot be null");
        this.processingContextBuilder = processingContextBuilder;
    }

    
    
    
    
    public void setDialects(final List dialects) {
        this.dialects = new ArrayList();
        this.dialects.addAll(dialects);
        this.dialects = Collections.unmodifiableList(dialects);
    }
    
    public List getDialects() {
        return this.dialects;
    }


    

    public void setThrottleStep(final int throttleStep) {
        this.throttleStep = throttleStep;
    }

    public int getThrottleStep() {
        return this.throttleStep;
    }




    public ThrottleType getThrottleType() {
        return this.throttleType;
    }

    public void setThrottleType(final ThrottleType throttleType) {
        this.throttleType = throttleType;
    }




    public void setReporter(final ITestReporter reporter) {
        Validate.notNull(reporter, "Reporter cannot be null");
        this.reporter = reporter;
    }
    
    public ITestReporter getReporter() {
        return this.reporter;
    }

    
    

    
    

    public void execute(final String testableName) {
        
        final TestExecutionContext context = new TestExecutionContext();
        final String executionId = context.getExecutionId();
        
        TestExecutor.setThreadExecutionId(executionId);
        
        try {
            
            final ITestable testable = this.testableResolver.resolve(executionId, testableName);
            if (testable == null) {
                throw new TestEngineExecutionException("Resource \"" + testableName + "\" could not be resolved.");
            }
            
            execute(testable, context);
            
        } catch (final TestEngineExecutionException e) {
            throw e;
        } catch (final Exception e) {
            throw new TestEngineExecutionException("Error executing testable \"" + testableName + "\"", e);
        }
        
    }

    
    
    private void execute(final ITestable testable, final TestExecutionContext context) {

        Validate.notNull(testable, "Testable cannot be null");
        Validate.notNull(context, "Test execution context cannot be null");
        
        final TestEngineTemplateResolver templateResolver = new TestEngineTemplateResolver();
        final TestEngineMessageResolver messageResolver = new TestEngineMessageResolver();
        final TestCacheManager cacheManager = new TestCacheManager();
        
        final TemplateEngine templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        templateEngine.setMessageResolver(messageResolver);
        templateEngine.setDialects(new HashSet(this.dialects));
        templateEngine.setCacheManager(cacheManager);
        
        context.setTemplateEngine(templateEngine);

        this.reporter.executionStart(context.getExecutionId());

        final TestExecutionResult result = executeTestable(testable, context);

        this.reporter.executionEnd(
                context.getExecutionId(), result.getTotalTestsOk(), result.getTotalTestsExecuted(),
                result.getTotalTimeNanos());

    }

    
    // protected in order to be accessed from parallelizer threads
    protected TestExecutionResult executeTestable(final ITestable testable, final TestExecutionContext context) {

        Validate.notNull(testable, "Testable cannot be null");
        
        if (testable instanceof ITestSequence) {
            return executeSequence((ITestSequence)testable, context);
        } else if (testable instanceof ITestIterator) {
            return executeIterator((ITestIterator)testable, context);
        } else if (testable instanceof ITestParallelizer) {
            return executeParallelizer((ITestParallelizer)testable, context);
        } else if (testable instanceof ITest) {
            return executeTest((ITest)testable, context);
        } else {
            // Should never happen
            throw new TestEngineExecutionException(
                    "ITestable implementation \"" + testable.getClass() + "\" is not recognized");
        }
        
        
    }
    
    
    private TestExecutionResult executeSequence(final ITestSequence sequence, final TestExecutionContext context) {

        Validate.notNull(sequence, "Sequence cannot be null");
        Validate.notNull(context, "Test execution context cannot be null");
        
        this.reporter.sequenceStart(context.getExecutionId(), context.getNestingLevel(), sequence);
        
        final TestExecutionResult result = new TestExecutionResult();
        
        final List elements = sequence.getElements();
        
        for (final ITestable element : elements) {
            result.addResult(executeTestable(element, context.nest()));
        }
        
        this.reporter.sequenceEnd(
                context.getExecutionId(), context.getNestingLevel(), sequence,
                result.getTotalTestsOk(), result.getTotalTestsExecuted(), 
                result.getTotalTimeNanos());
        
        return result;
        
    }
    
    
    
    private TestExecutionResult executeIterator(final ITestIterator iterator, final TestExecutionContext context) {

        Validate.notNull(iterator, "Iterator cannot be null");
        Validate.notNull(context, "Test execution context cannot be null");

        this.reporter.iteratorStart(context.getExecutionId(), context.getNestingLevel(), iterator);
        
        final int iterations = iterator.getIterations();
        final ITestable element = iterator.getIteratedElement();
        
        final TestExecutionResult result = new TestExecutionResult();
        final TestExecutionContext iterationContext = context.nest();
        
        for (int i = 1; i <= iterations; i++) {
            
            this.reporter.iterationStart(context.getExecutionId(), iterationContext.getNestingLevel(), iterator, i);

            final TestExecutionResult elementResult = executeTestable(element, iterationContext.nest());
            
            this.reporter.iterationEnd(
                    context.getExecutionId(), iterationContext.getNestingLevel(), iterator, i,
                    elementResult.getTotalTestsOk(), elementResult.getTotalTestsExecuted(), 
                    elementResult.getTotalTimeNanos());
            
            result.addResult(elementResult);
            
        }
        
        this.reporter.iteratorEnd(
                context.getExecutionId(), context.getNestingLevel(), iterator,
                result.getTotalTestsOk(), result.getTotalTestsExecuted(), 
                result.getTotalTimeNanos());
        
        return result;
        
    }
    
    
    
    private TestExecutionResult executeParallelizer(final ITestParallelizer parallelizer, final TestExecutionContext context) {

        Validate.notNull(parallelizer, "Parallelizer cannot be null");
        Validate.notNull(context, "Test execution context cannot be null");

        final int numThreads = parallelizer.getNumThreads();
        final List> tasks = new ArrayList>();
        
        final TestExecutionResult result = new TestExecutionResult();
        
        final ThreadPoolExecutor threadExecutor = 
                new ThreadPoolExecutor(numThreads, numThreads, Long.MAX_VALUE, TimeUnit.NANOSECONDS, new SynchronousQueue());
        
        this.reporter.parallelizerStart(context.getExecutionId(), context.getNestingLevel(), parallelizer);
        
        for (int i = 1; i <= numThreads; i++) {
            final ExecutorTask task =  new ExecutorTask(this, parallelizer, context, i);
            final FutureTask futureTask = new FutureTask(task);
            tasks.add(futureTask);
            threadExecutor.execute(futureTask);
        }

        for (final FutureTask futureTask : tasks) {
            try {
                result.addResult(futureTask.get());
            } catch (final Throwable t) {
                t.printStackTrace();
            }
        }
        
        threadExecutor.shutdown();
        
        this.reporter.parallelizerEnd(
                context.getExecutionId(), context.getNestingLevel(), parallelizer,
                result.getTotalTestsOk(), result.getTotalTestsExecuted(), 
                result.getTotalTimeNanos());
        
        return result;
        
    }
    
    
    
    private TestExecutionResult executeTest(final ITest test, final TestExecutionContext context) {

        Validate.notNull(test, "Test cannot be null");
        Validate.notNull(context, "Test execution context cannot be null");

        final String executionId = context.getExecutionId();
        final String testName = context.getTestNamer().nameTest(test);
        final TemplateEngine templateEngine = context.getTemplateEngine();
        
        setThreadTest(test);
        setThreadTestName(testName);
        
        this.reporter.testStart(executionId, context.getNestingLevel(), test, testName);
        
        final String fragmentSpec = test.getFragmentSpec();
        final Set markupSelectors = fragmentSpec == null? null : Collections.singleton(fragmentSpec);
        
        final IContext processingContext = this.processingContextBuilder.build(test);
        
        final StringWriter writer = new StringWriter();

        ITestResult testResult = null;
        
        long startTimeNanos = System.nanoTime();
        long endTimeNanos;
        try {

            if (this.throttleStep <= 0 || this.throttleStep == Integer.MAX_VALUE) {
                templateEngine.process(testName, markupSelectors, processingContext, writer);
            } else {
                final IThrottledTemplateProcessor throttledProcessor = templateEngine.processThrottled(testName, markupSelectors, processingContext);
                if (this.throttleType == ThrottleType.CHARS) {
                    while (!throttledProcessor.isFinished()) {
                        throttledProcessor.process(this.throttleStep, writer);
                    }
                } else {
                    final ByteArrayOutputStream baos = new ByteArrayOutputStream(200);
                    while (!throttledProcessor.isFinished()) {
                        throttledProcessor.process(this.throttleStep, baos, Charset.forName("UTF-8"));
                    }
                    writer.write(new String(baos.toByteArray(), "UTF-8"));
                }
            }
            endTimeNanos = System.nanoTime();
            
            final String result = writer.toString();
            testResult = test.evalResult(executionId, testName, result);
            
        } catch (final Throwable t) {
            endTimeNanos = System.nanoTime();
            testResult = test.evalResult(executionId, testName, t);
        }
        
        final long totalTimeNanos = (endTimeNanos - startTimeNanos);
        
        this.reporter.testEnd(executionId, context.getNestingLevel(), test, testName, testResult, totalTimeNanos);
        
        final TestExecutionResult result = new TestExecutionResult();
        result.addTestResult(testResult.isOK(), totalTimeNanos);

        setThreadTestName(null);
        setThreadTest(null);
        
        return result;
        
    }
    

    
    
    public final void reset() {
        if (this.reporter != null) {
            this.reporter.reset();
        }
    }
    
    
    public final boolean isAllOK() {
        if (this.reporter != null) {
            return this.reporter.isAllOK();
        }
        throw new TestEngineExecutionException(
                "Cannot execute 'isAllOK()' call: no test reporter has been set");
    }
    
    
    
    
    static class ExecutorTask implements Callable {

        private final TestExecutor executor;
        private final TestExecutionContext context;
        private final ITestParallelizer parallelizer;
        private final int threadNumber;
        

                
        ExecutorTask(final TestExecutor executor,
                final ITestParallelizer parallelizer,  final TestExecutionContext context,
                final int threadNumber) {
            super();
            this.executor = executor;
            this.context = context;
            this.parallelizer = parallelizer;
            this.threadNumber = threadNumber;
        }



        public TestExecutionResult call() {

            final TestExecutionContext threadExecutionContext = this.context.nest();
            
            TestExecutor.setThreadExecutionId(threadExecutionContext.getExecutionId());
            
            final ITestable parallelizedElement = this.parallelizer.getParallelizedElement();
            
            this.executor.reporter.parallelThreadStart(
                    this.context.getExecutionId(), threadExecutionContext.getNestingLevel(), this.parallelizer, this.threadNumber);
            
            final TestExecutionResult result =
                    this.executor.executeTestable(parallelizedElement, threadExecutionContext.nest());
            
            this.executor.reporter.parallelThreadEnd(
                    this.context.getExecutionId(), threadExecutionContext.getNestingLevel(), this.parallelizer, this.threadNumber,
                    result.getTotalTestsOk(), result.getTotalTestsExecuted(),
                    result.getTotalTimeNanos());

            return result;
            
        }
        
        
    }
    
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy