Package restx.tests

Source Code of restx.tests.RestxSpecTestServer$RunningServer$Rules

package restx.tests;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.io.Files;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import restx.RestxContext;
import restx.config.Settings;
import restx.config.SettingsKey;
import restx.classloader.ClasspathResourceEvent;
import restx.classloader.CompilationFinishedEvent;
import restx.common.UUIDGenerator;
import restx.exceptions.ErrorCode;
import restx.exceptions.ErrorField;
import restx.exceptions.RestxErrors;
import restx.factory.AutoStartable;
import restx.factory.Factory;
import restx.factory.NamedComponent;
import restx.factory.SingletonFactoryMachine;
import restx.server.WebServer;
import restx.server.WebServerSupplier;
import restx.specs.HotReloadRestxSpecRepository;
import restx.specs.RestxSpecLoader;
import restx.specs.RestxSpecRepository;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;

import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.Executors.newSingleThreadExecutor;

/**
* User: xavierhanin
* Date: 7/30/13
* Time: 9:23 PM
*/
public class RestxSpecTestServer {

    /**
     * A shortcut for new RestxSpecRule("/api", 8076, queryByClass(WebServerSupplier.class), Factory.getInstance())
     */
    public static RestxSpecTestServer newInstance() {
        Factory f = Factory.getInstance();
        return new RestxSpecTestServer("/api", 8076, f.getComponent(WebServerSupplier.class), f);
    }

    /**
     * A shortcut for new RestxSpecRule("/api", 8076, webServerSupplier, Factory.getInstance())
     */
    public static RestxSpecTestServer newInstance(WebServerSupplier webServerSupplier) {
        return new RestxSpecTestServer("/api", 8076, webServerSupplier, Factory.getInstance());
    }

    private final String routerPath;
    private final int port;
    private final WebServerSupplier webServerSupplier;
    private final Factory factory;

    @Settings
    public static interface RunningServerSettings {
        @SettingsKey(key = "restx.targetTestsRoot", defaultValue = "target/restx/tests")
        String targetTestsRoot();
    }

    public static class RunningServer {
        private static final Logger logger = LoggerFactory.getLogger(RunningServer.class);

        private final WebServer server;
        private final RestxSpecRunner runner;
        private final RestxSpecRepository repository;
        private final RestxErrors errors;
        private final UUIDGenerator uuidGenerator;
        private final ExecutorService testRequestExecutor = newSingleThreadExecutor();
        private final ListeningExecutorService testExecutor = listeningDecorator(newFixedThreadPool(4));
        private final Path storeLocation;
        private final ObjectMapper objectMapper;
        private final Map<String, TestResultSummary> lastResults;
        private final PrintStream sysout = System.out;
        private final PrintStream syserr = System.err;
        private final ThreadLocalPrintStream out = new ThreadLocalPrintStream(sysout);
        private final ThreadLocalPrintStream err = new ThreadLocalPrintStream(syserr);

        public RunningServer(WebServer server, RestxSpecRunner runner, RestxSpecRepository repository,
                             UUIDGenerator uuidGenerator,
                             RestxErrors errors,
                             RunningServerSettings settings) {
            this.server = server;
            this.runner = runner;
            this.repository = repository;
            this.uuidGenerator = uuidGenerator;
            this.errors = errors;

            storeLocation = Paths.get(settings.targetTestsRoot());

            objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JodaModule());
            objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

            lastResults = loadLastResults();

            System.setOut(out);
            System.setErr(err);


            Factory.LocalMachines.contextLocal(server.getServerId()).addMachine(
                    new SingletonFactoryMachine<>(0, NamedComponent.of(RunningServer.class, "RunningServer", this)));
        }

        public WebServer getServer() {
            return server;
        }

        public void stop() throws Exception {
            runner.dispose();
            server.stop();
            System.setOut(sysout);
            System.setErr(syserr);
        }

        public TestRequest submitTestRequest(TestRequest testRequest) {
            if (testRequest.getTest().startsWith("specs")) {
                final String requestKey = uuidGenerator.doGenerate();
                logger.info("queuing test request {}", testRequest);
                testRequest.setKey(requestKey);
                testRequest.setRequestTime(DateTime.now());
                testRequest.setStatus(TestRequest.Status.QUEUED);
                store(testRequest);
                testRequestExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        Optional<TestRequest> requestOptional = getRequestByKey(requestKey);
                        if (!requestOptional.isPresent()) {
                            logger.warn("test request not found when trying to execute it: {}", requestKey);
                            return;
                        }
                        Stopwatch stopwatch = Stopwatch.createStarted();
                        TestRequest testRequest = requestOptional.get();
                        logger.info("running test request {}", testRequest);
                        testRequest.setStatus(TestRequest.Status.RUNNING);
                        store(testRequest);

                        List<ListenableFuture<String>> futureResultKeys = new ArrayList<>();
                        String spec = testRequest.getTest();
                        if (spec.endsWith("*")) {
                            // clear last results when we run all tests
                            // this is currently the only way to cleanup last results
                            if (spec.equals("specs/*")) {
                                synchronized (lastResults) {
                                    lastResults.clear();
                                }
                            }

                            String prefix = spec.substring(0, spec.length() - 1);
                            for (String s : repository.findAll()) {
                                if (s.startsWith(prefix)) {
                                    futureResultKeys.add(testExecutor.submit(runSpecTest(s)));
                                }
                            }
                        } else {
                            futureResultKeys.add(testExecutor.submit(runSpecTest(spec)));
                        }

                        List<String> resultKeys = Futures.getUnchecked(Futures.allAsList(futureResultKeys));
                        testRequest.setStatus(TestRequest.Status.DONE);
                        testRequest.setTestResultKey(Joiner.on(",").join(resultKeys));
                        store(testRequest);
                        logger.info("completed test request {} in {}: {}", testRequest.getKey(), stopwatch.stop(), testRequest);
                    }
                });

                return testRequest;
            } else {
                throw errors.on(Rules.InvalidTest.class)
                        .set(Rules.InvalidTest.TEST, testRequest.getTest())
                        .set(Rules.InvalidTest.DESCRIPTION, "can only run spec test, test field must start with 'specs'")
                        .raise();
            }
        }

        private Callable<String> runSpecTest(final String spec) {
            return new Callable<String>() {
                @Override
                public String call() throws Exception {
                    logger.info("spec test {} >> STARTING", spec);
                    Stopwatch stopWatch = Stopwatch.createStarted();
                    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                    final PrintStream outPrintStream = new PrintStream(outStream);
                    out.setCurrent(outPrintStream);
                    ByteArrayOutputStream errStream = new ByteArrayOutputStream();
                    final PrintStream errPrintStream = new PrintStream(errStream);
                    err.setCurrent(errPrintStream);

                    // propagate streams on server
                    Factory.LocalMachines.threadLocal()
                            .set("OutPrintStreamComponent", new LocalStreamComponent(out, outPrintStream))
                            .set("ErrPrintStreamComponent", new LocalStreamComponent(err, errPrintStream))
                    ;

                    TestResultSummary.Status status = TestResultSummary.Status.ERROR;
                    long start = System.currentTimeMillis();
                    try {
                        runner.runTest(repository.findSpecById(spec).get());
                        status = TestResultSummary.Status.SUCCESS;
                    } catch (AssertionError e) {
                        status = TestResultSummary.Status.FAILURE;
                        System.err.println(e.getMessage());
                    } catch (Throwable e) {
                        e.printStackTrace(System.err);
                    } finally {
                        out.clearCurrent();
                        err.clearCurrent();
                    }

                    TestResult result = new TestResult()
                            .setSummary(new TestResultSummary()
                                    .setKey(uuidGenerator.doGenerate())
                                    .setName(spec)
                                    .setStatus(status)
                                    .setTestDuration(System.currentTimeMillis() - start)
                                    .setTestTime(new DateTime(start))
                            )
                            .setStdOut(new String(outStream.toByteArray()))
                            .setStdErr(new String(errStream.toByteArray()))
                            ;
                    store(result);
                    logger.info("spec test {} >> END {}", spec, stopWatch);
                    return result.getSummary().getKey();
                }
            };
        }

        private void store(TestRequest testRequest) {
            try {
                File file = testRequestFile(testRequest.getKey());
                file.getParentFile().mkdirs();
                objectMapper.writeValue(file, testRequest);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Optional<TestRequest> getRequestByKey(String key) {
            File file = testRequestFile(key);
            if (!file.exists()) {
                return Optional.absent();
            }

            try {
                TestRequest testRequest = objectMapper.readValue(file, TestRequest.class);
                return Optional.of(testRequest);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private File testRequestFile(String key) {
            return storeLocation.resolve(
                    "requests/" + key + ".json").toFile();
        }

        private Map<String,TestResultSummary> loadLastResults() {
            Map<String, TestResultSummary> results = new HashMap<>();

            File src = lastResultSummariesFile();
            if (!src.exists()) {
                return results;
            }

            try {
                Collection<TestResultSummary> summaries = objectMapper.readValue(src,
                        new TypeReference<Collection<TestResultSummary>>() { });

                for (TestResultSummary summary : summaries) {
                    results.put(summary.getName(), summary);
                }
            } catch (IOException e) {
                logger.error("error reading last result summaries file - will start with empty data", e);
                results.clear();
            }

            return results;
        }

        private void store(TestResult result) {
            try {
                String key = result.getSummary().getKey();
                File resultFile = testResultSummaryFile(key);
                resultFile.getParentFile().mkdirs();

                Files.write(result.getStdOut(), testResultStdOutFile(key), Charsets.UTF_8);
                Files.write(result.getStdErr(), testResultStdErrFile(key), Charsets.UTF_8);
                objectMapper.writeValue(resultFile, result.getSummary());

                synchronized (lastResults) {
                    lastResults.put(result.getSummary().getName(), result.getSummary());
                    testRequestExecutor.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                objectMapper.writeValue(lastResultSummariesFile(), lastResults.values());
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    });
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Optional<TestResult> getResultByKey(String key) {
            File file = testResultSummaryFile(key);
            if (!file.exists()) {
                return Optional.absent();
            }

            try {
                TestResult testResult = new TestResult()
                        .setSummary(objectMapper.readValue(file, TestResultSummary.class))
                        .setStdOut(Files.toString(testResultStdOutFile(key), Charsets.UTF_8))
                        .setStdErr(Files.toString(testResultStdErrFile(key), Charsets.UTF_8))
                        ;
                return Optional.of(testResult);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private File lastResultSummariesFile() {
            return storeLocation.resolve(
                    "results/last.summaries.json").toFile();
        }

        private File testResultStdOutFile(String key) {
            return storeLocation.resolve(
                    "results/" + key + ".stdout.txt").toFile();
        }

        private File testResultStdErrFile(String key) {
            return storeLocation.resolve(
                    "results/" + key + ".stderr.txt").toFile();
        }

        private File testResultSummaryFile(String key) {
            return storeLocation.resolve(
                    "results/" + key + ".summary.json").toFile();
        }

        public Iterable<TestResultSummary> findCurrentTestResults() {
            synchronized (lastResults) {
                return new ArrayList<>(lastResults.values());
            }
        }

        public static class Rules {
            @ErrorCode(code = "TEST-001", description = "invalid test")
            public static enum InvalidTest {
                @ErrorField("requested test") TEST,
                @ErrorField("description why the test is invalid") DESCRIPTION
            }
        }

        private static class LocalStreamComponent implements AutoStartable, AutoCloseable {
            private final ThreadLocalPrintStream localPrintStream;
            private final PrintStream stream;

            private LocalStreamComponent(ThreadLocalPrintStream localPrintStream, PrintStream stream) {
                this.localPrintStream = localPrintStream;
                this.stream = stream;
            }

            @Override
            public void start() {
                localPrintStream.setCurrent(stream);
            }

            @Override
            public void close() throws Exception {
                localPrintStream.clearCurrent();
            }
        }
    }

    /**
     * Constructs a new RestxSpecRule.
     *
     * @param routerPath the path at which restx router is mounted. eg '/api'
     * @param webServerSupplier a supplier of WebServer, you can use #jettyWebServerSupplier for jetty.
     * @param factory the restx Factory to use to find GivenSpecRuleSupplier s when executing the spec.
     *                This is not used for the server itself.
     */
    public RestxSpecTestServer(String routerPath, int port, WebServerSupplier webServerSupplier, Factory factory) {
        this.routerPath = routerPath;
        this.port = port;
        this.webServerSupplier = webServerSupplier;
        this.factory = factory;
    }

    public RunningServer start() throws Exception {
        System.setProperty("restx.mode", RestxContext.Modes.INFINIREST);
        WebServer server = webServerSupplier.newWebServer(port);
        server.start();
        RestxSpecLoader specLoader = new RestxSpecLoader(factory);
        RestxSpecRunner runner = new RestxSpecRunner(specLoader, routerPath, server.getServerId(), server.baseUrl(), factory);
        RestxSpecRepository repository = new HotReloadRestxSpecRepository(specLoader);

        final RunningServer runningServer = new RunningServer(
                server, runner, repository,
                factory.getComponent(UUIDGenerator.class),
                factory.getComponent(RestxErrors.class),
                factory.getComponent(RunningServerSettings.class));

        Factory.getFactory(server.getServerId()).get().getComponent(EventBus.class).register(new Object() {
            @Subscribe
            public void onCompilationFinished(
                    CompilationFinishedEvent event) {
                runningServer.submitTestRequest(new TestRequest().setTest("specs/*"));
            }

            @Subscribe
            public void onResourceEvent(ClasspathResourceEvent event) {
                if (event.getResourcePath().startsWith("specs")) {
                    runningServer.submitTestRequest(new TestRequest().setTest(event.getResourcePath()));
                } else {
                    runningServer.submitTestRequest(new TestRequest().setTest("specs/*"));
                }
            }
        });

        return runningServer;
    }

    public static void main(String[] args) throws Exception {
        RestxSpecTestServer.newInstance().start();
    }
}
TOP

Related Classes of restx.tests.RestxSpecTestServer$RunningServer$Rules

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.