com.asakusafw.lang.compiler.cli.BatchCompilerCliTest.java Source code

Java tutorial

Introduction

Here is the source code for com.asakusafw.lang.compiler.cli.BatchCompilerCliTest.java

Source

/**
 * 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.lang.compiler.cli;

import static com.asakusafw.lang.compiler.model.description.Descriptions.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.cli.ParseException;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;

import com.asakusafw.lang.compiler.api.CompilerOptions;
import com.asakusafw.lang.compiler.cli.BatchCompilerCli.Configuration;
import com.asakusafw.lang.compiler.cli.mock.DelegateBatchCompiler;
import com.asakusafw.lang.compiler.cli.mock.DummyBatch;
import com.asakusafw.lang.compiler.cli.mock.DummyBatchCompiler;
import com.asakusafw.lang.compiler.cli.mock.DummyBatchProcessor;
import com.asakusafw.lang.compiler.cli.mock.DummyClassAnalyzer;
import com.asakusafw.lang.compiler.cli.mock.DummyCompilerParticipant;
import com.asakusafw.lang.compiler.cli.mock.DummyDataModelProcessor;
import com.asakusafw.lang.compiler.cli.mock.DummyExternalPortProcessor;
import com.asakusafw.lang.compiler.cli.mock.DummyJobflowProcessor;
import com.asakusafw.lang.compiler.common.Diagnostic;
import com.asakusafw.lang.compiler.common.DiagnosticException;
import com.asakusafw.lang.compiler.common.Location;
import com.asakusafw.lang.compiler.common.Predicates;
import com.asakusafw.lang.compiler.common.testing.FileDeployer;
import com.asakusafw.lang.compiler.core.BatchCompiler;
import com.asakusafw.lang.compiler.core.util.CompositeElement;
import com.asakusafw.lang.compiler.packaging.ResourceRepository;
import com.asakusafw.lang.compiler.packaging.ResourceUtil;

/**
 * Test for {@link BatchCompilerCli}.
 */
public class BatchCompilerCliTest {

    /**
     * deployer.
     */
    @Rule
    public FileDeployer deployer = new FileDeployer();

    /**
     * empty args.
     * @throws Exception if failed
     */
    @Test(expected = ParseException.class)
    public void parse_empty() throws Exception {
        BatchCompilerCli.parse();
    }

    /**
     * minimal args.
     * @throws Exception if failed
     */
    @Test
    public void parse_minimal() throws Exception {
        File input = deployer.newFolder();
        File output = deployer.newFolder();
        Configuration conf = BatchCompilerCli
                .parse(strings(new Object[] { "--explore", input, "--output", output, }));
        assertThat(conf.classAnalyzer, containsInAnyOrder(BatchCompilerCli.DEFAULT_CLASS_ANALYZER));
        assertThat(conf.batchCompiler, containsInAnyOrder(BatchCompilerCli.DEFAULT_BATCH_COMPILER));
        assertThat(conf.output, contains(output));
        assertThat(conf.explore, contains(input));
        assertThat(conf.external, isEmpty());
        assertThat(conf.embed, isEmpty());
        assertThat(conf.attach, isEmpty());
        assertThat(conf.sourcePredicate, isEmpty());
        assertThat(conf.runtimeWorkingDirectory, contains(BatchCompilerCli.DEFAULT_RUNTIME_WORKING_DIRECTORY));
        assertThat(conf.properties.entrySet(), is(empty()));
        assertThat(conf.failOnError, contains(false));
        assertThat(conf.batchIdPrefix, isEmpty());
    }

    /**
     * full args.
     * @throws Exception if failed
     */
    @Test
    public void parse_full() throws Exception {
        File input = deployer.newFolder();
        File output = deployer.newFolder();
        File external1 = deployer.newFolder();
        File external2 = deployer.newFolder();
        File embed1 = deployer.newFolder();
        File embed2 = deployer.newFolder();
        File attach1 = deployer.newFolder();
        File attach2 = deployer.newFolder();
        Configuration conf = BatchCompilerCli.parse(strings(new Object[] { "--explore", input, "--output", output,
                "--classAnalyzer", classes(DummyClassAnalyzer.class), "--batchCompiler",
                classes(DummyBatchCompiler.class), "--external", files(external1, external2), "--embed",
                files(embed1, embed2), "--attach", files(attach1, attach2), "--include", "*Buffer", "--exclude",
                "java.lang.*", "--dataModelProcessors", classes(DummyDataModelProcessor.class),
                "--externalPortProcessors", classes(DummyExternalPortProcessor.class), "--batchProcessors",
                classes(DummyBatchProcessor.class), "--jobflowProcessors", classes(DummyJobflowProcessor.class),
                "--participants", classes(DummyCompilerParticipant.class), "--runtimeWorkingDirectory",
                "testRuntimeWorkingDirectory", "--failOnError", true, "--batchIdPrefix", "prefix.", "-P", "a=b",
                "-property", "c=d", }));
        assertThat(conf.classAnalyzer, containsInAnyOrder(classOf(DummyClassAnalyzer.class)));
        assertThat(conf.batchCompiler, containsInAnyOrder(classOf(DummyBatchCompiler.class)));
        assertThat(conf.output, contains(output));
        assertThat(conf.explore, contains(input));
        assertThat(conf.external, containsInAnyOrder(external1, external2));
        assertThat(conf.embed, containsInAnyOrder(embed1, embed2));
        assertThat(conf.attach, containsInAnyOrder(attach1, attach2));
        assertThat(conf.dataModelProcessors, containsInAnyOrder(classOf(DummyDataModelProcessor.class)));
        assertThat(conf.externalPortProcessors, containsInAnyOrder(classOf(DummyExternalPortProcessor.class)));
        assertThat(conf.batchProcessors, containsInAnyOrder(classOf(DummyBatchProcessor.class)));
        assertThat(conf.jobflowProcessors, containsInAnyOrder(classOf(DummyJobflowProcessor.class)));
        assertThat(conf.compilerParticipants, containsInAnyOrder(classOf(DummyCompilerParticipant.class)));
        assertThat(conf.sourcePredicate, not(isEmpty()));
        assertThat(conf.runtimeWorkingDirectory, contains("testRuntimeWorkingDirectory"));
        assertThat(conf.properties.entrySet(), hasSize(2));
        assertThat(conf.failOnError, contains(true));
        assertThat(conf.batchIdPrefix, contains("prefix."));

        Predicate<? super Class<?>> p = predicate(conf.sourcePredicate);
        assertThat(p.test(ByteBuffer.class), is(true));
        assertThat(p.test(ByteChannel.class), is(false));
        assertThat(p.test(StringBuffer.class), is(false));

        assertThat(conf.properties, hasEntry("a", "b"));
        assertThat(conf.properties, hasEntry("c", "d"));
    }

    /**
     * {@code --include} multiple patterns.
     * @throws Exception if failed
     */
    @Test
    public void parse_include() throws Exception {
        Configuration conf = BatchCompilerCli.parse(strings(new Object[] { "--explore", deployer.newFolder(),
                "--output", deployer.newFolder(), "--include", "*.String*,*Buffer", }));
        Predicate<? super Class<?>> p = predicate(conf.sourcePredicate);
        assertThat(p.test(String.class), is(true));
        assertThat(p.test(StringBuilder.class), is(true));
        assertThat(p.test(ByteBuffer.class), is(true));
        assertThat(p.test(Integer.class), is(false));
        assertThat(p.test(ByteChannel.class), is(false));
    }

    /**
     * {@code --exclude} multiple patterns.
     * @throws Exception if failed
     */
    @Test
    public void parse_exclude() throws Exception {
        Configuration conf = BatchCompilerCli.parse(strings(new Object[] { "--explore", deployer.newFolder(),
                "--output", deployer.newFolder(), "--exclude", "*.String*,*Buffer", }));
        Predicate<? super Class<?>> p = predicate(conf.sourcePredicate);
        assertThat(p.test(String.class), is(false));
        assertThat(p.test(StringBuilder.class), is(false));
        assertThat(p.test(ByteBuffer.class), is(false));
        assertThat(p.test(Integer.class), is(true));
        assertThat(p.test(ByteChannel.class), is(true));
    }

    /**
     * empty args.
     * @throws Exception if failed
     */
    @Test
    public void execute_empty() throws Exception {
        int status = BatchCompilerCli.execute();
        assertThat(status, is(not(0)));
    }

    /**
     * minimal args.
     * @throws Exception if failed
     */
    @Test
    public void execute_minimal() throws Exception {
        File output = deployer.newFolder();
        String[] args = strings(
                new Object[] { "--explore", files(ResourceUtil.findLibraryByClass(DummyBatch.class)), "--output",
                        output, "--classAnalyzer", classes(DummyClassAnalyzer.class), "--batchCompiler",
                        classes(DelegateBatchCompiler.class), "--include", classes(DummyBatch.class),
                        "--externalPortProcessors", classes(DummyExternalPortProcessor.class), });
        AtomicInteger count = new AtomicInteger();
        int status = execute(args, (context, batch) -> {
            count.incrementAndGet();
            assertThat(batch.getBatchId(), is("DummyBatch"));
            assertThat(batch.getDescriptionClass(), is(classOf(DummyBatch.class)));
            assertThat(context.getOutput().getBasePath(), is(new File(output, batch.getBatchId())));
        });
        assertThat(status, is(0));
        assertThat(count.get(), is(1));
    }

    /**
     * full args.
     * @throws Exception if failed
     */
    @Test
    public void execute_full() throws Exception {
        File output = deployer.newFolder();
        File explore = prepareLibrary("explore");
        File external = prepareLibrary("external");
        File embed = prepareLibrary("embed");
        File attach = prepareLibrary("attach");
        String[] args = strings(new Object[] { "--explore",
                files(ResourceUtil.findLibraryByClass(DummyBatch.class), explore), "--output", output,
                "--classAnalyzer", classes(DummyClassAnalyzer.class), "--batchCompiler",
                classes(DelegateBatchCompiler.class), "--external", files(external), "--embed", files(embed),
                "--attach", files(attach), "--include", classes(DummyBatch.class), "--dataModelProcessors",
                classes(DummyDataModelProcessor.class), "--externalPortProcessors",
                classes(DummyExternalPortProcessor.class), "--batchProcessors", classes(DummyBatchProcessor.class),
                "--jobflowProcessors", classes(DummyJobflowProcessor.class), "--participants",
                classes(DummyCompilerParticipant.class), "--runtimeWorkingDirectory", "testRuntimeWorkingDirectory",
                "--failOnError", true, "--batchIdPrefix", "prefix.", "-P", "a=b", "-property", "c=d", });
        AtomicInteger count = new AtomicInteger();
        int status = execute(args, (context, batch) -> {
            count.incrementAndGet();
            assertThat(batch.getBatchId(), is("prefix.DummyBatch"));
            assertThat(batch.getDescriptionClass(), is(classOf(DummyBatch.class)));
            assertThat(context.getOutput().getBasePath(), is(new File(output, batch.getBatchId())));

            assertThat(context.getProject().getClassLoader().getResource("explore"), is(notNullValue()));
            assertThat(context.getProject().getClassLoader().getResource("embed"), is(notNullValue()));
            assertThat(context.getProject().getClassLoader().getResource("attach"), is(notNullValue()));
            assertThat(context.getProject().getClassLoader().getResource("external"), is(notNullValue()));

            assertThat(context.getProject().getProjectContents(), includes("explore"));
            assertThat(context.getProject().getProjectContents(), not(includes("embed")));
            assertThat(context.getProject().getProjectContents(), not(includes("attach")));
            assertThat(context.getProject().getProjectContents(), not(includes("external")));

            // --explore -> implicitly embedded
            assertThat(context.getProject().getEmbeddedContents(), includes("explore"));
            assertThat(context.getProject().getEmbeddedContents(), includes("embed"));
            assertThat(context.getProject().getEmbeddedContents(), not(includes("attach")));
            assertThat(context.getProject().getEmbeddedContents(), not(includes("external")));

            assertThat(context.getProject().getAttachedLibraries(), not(deepIncludes("explore")));
            assertThat(context.getProject().getAttachedLibraries(), not(deepIncludes("embed")));
            assertThat(context.getProject().getAttachedLibraries(), deepIncludes("attach"));
            assertThat(context.getProject().getAttachedLibraries(), not(deepIncludes("external")));

            assertThat(context.getTools().getDataModelProcessor(), is(consistsOf(DummyDataModelProcessor.class)));
            assertThat(context.getTools().getExternalPortProcessor(),
                    is(consistsOf(DummyExternalPortProcessor.class)));
            assertThat(context.getTools().getBatchProcessor(), is(consistsOf(DummyBatchProcessor.class)));
            assertThat(context.getTools().getJobflowProcessor(), is(consistsOf(DummyJobflowProcessor.class)));
            assertThat(context.getTools().getParticipant(), is(consistsOf(DummyCompilerParticipant.class)));
        });
        assertThat(status, is(0));
        assertThat(count.get(), is(1));
    }

    /**
     * w/ scoped properties.
     * @throws Exception if failed
     */
    @Test
    public void execute_scoped_properties() throws Exception {
        File output = deployer.newFolder();
        String[] args = strings(new Object[] { "--explore",
                files(ResourceUtil.findLibraryByClass(DummyBatch.class)), "--output", output, "--classAnalyzer",
                classes(DummyClassAnalyzer.class), "--batchCompiler", classes(DelegateBatchCompiler.class),
                "--include", classes(DummyBatch.class), "--externalPortProcessors",
                classes(DummyExternalPortProcessor.class), "--batchIdPrefix", "prefix.", "-P", "a=A", "-P", "b=B",
                "-P", "DummyBatch:b=!", "-P", "DummyBatch:c=C", "-P", "other:a=INVALID", });
        AtomicInteger count = new AtomicInteger();
        int status = execute(args, (context, batch) -> {
            count.incrementAndGet();
            CompilerOptions options = context.getOptions();
            assertThat(options.get("a", "?"), is("A"));
            assertThat(options.get("b", "?"), is("!"));
            assertThat(options.get("c", "?"), is("C"));
        });
        assertThat(status, is(0));
        assertThat(count.get(), is(1));
    }

    /**
     * execute w/ conflict batch ID.
     * @throws Exception if failed
     */
    @Test
    public void execute_conflict_batch() throws Exception {
        File output = deployer.newFolder();
        String[] args = strings(
                new Object[] { "--explore", files(ResourceUtil.findLibraryByClass(DummyBatch.class)), "--output",
                        output, "--classAnalyzer", classes(DummyClassAnalyzer.class), "--batchCompiler",
                        classes(DelegateBatchCompiler.class), "--include", DummyBatch.class.getName() + "*",
                        "--externalPortProcessors", classes(DummyExternalPortProcessor.class), "--failOnError", });
        int status = execute(args, (context, batch) -> {
            // do nothing
        });
        assertThat(status, is(not(0)));
    }

    private File prepareLibrary(String id) {
        File base = deployer.newFolder();
        try {
            assertThat(new File(base, id).createNewFile(), is(true));
        } catch (IOException e) {
            throw new AssertionError(e);
        }
        return base;
    }

    static Matcher<List<ResourceRepository>> includes(String id) {
        Location location = Location.of(id);
        return new BaseMatcher<List<ResourceRepository>>() {
            @Override
            public boolean matches(Object item) {
                List<?> elements = (List<?>) item;
                for (Object element : elements) {
                    ResourceRepository repo = (ResourceRepository) element;
                    try (ResourceRepository.Cursor cursor = repo.createCursor()) {
                        while (cursor.next()) {
                            if (cursor.getLocation().equals(location)) {
                                return true;
                            }
                        }
                    } catch (IOException e) {
                        throw new AssertionError(e);
                    }
                }
                return false;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("includes ").appendValue(location);
            }
        };
    }

    static Matcher<List<ResourceRepository>> deepIncludes(String id) {
        return new BaseMatcher<List<ResourceRepository>>() {
            @Override
            public boolean matches(Object item) {
                List<?> elements = (List<?>) item;
                for (Object element : elements) {
                    ResourceRepository repo = (ResourceRepository) element;
                    try (ResourceRepository.Cursor cursor = repo.createCursor()) {
                        while (cursor.next()) {
                            try (ZipInputStream input = new ZipInputStream(cursor.openResource())) {
                                while (true) {
                                    ZipEntry entry = input.getNextEntry();
                                    if (entry == null) {
                                        break;
                                    }
                                    if (entry.getName().equals(id)) {
                                        return true;
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        throw new AssertionError(e);
                    }
                }
                return false;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("deep includes ").appendValue(id);
            }
        };
    }

    static Matcher<Object> consistsOf(Class<?> type) {
        return new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object item) {
                return consistsOf(item, type);
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("consists of ").appendText(type.getName());
            }
        };
    }

    static boolean consistsOf(Object item, Class<?> type) {
        if (type.isInstance(item)) {
            return true;
        } else if (item instanceof CompositeElement<?>) {
            for (Object element : ((CompositeElement<?>) item).getElements()) {
                if (consistsOf(element, type)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * w/ compile error.
     * @throws Exception if failed
     */
    @Test
    public void compile_error() throws Exception {
        File output = deployer.newFolder();
        String[] args = strings(
                new Object[] { "--explore", files(ResourceUtil.findLibraryByClass(DummyBatch.class)), "--output",
                        output, "--classAnalyzer", classes(DummyClassAnalyzer.class), "--batchCompiler",
                        classes(DelegateBatchCompiler.class), "--include", classes(DummyBatch.class),
                        "--externalPortProcessors", classes(DummyExternalPortProcessor.class), });
        int status = execute(args, (context, batch) -> {
            throw new DiagnosticException(Diagnostic.Level.ERROR, "testing");
        });
        assertThat(status, is(not(0)));
    }

    private int execute(String[] args, BatchCompiler delegate) {
        DelegateBatchCompiler.DELEGATE.set(delegate);
        try {
            return BatchCompilerCli.execute(args);
        } finally {
            DelegateBatchCompiler.DELEGATE.remove();
        }
    }

    private String classes(Class<?>... classes) {
        StringBuilder buf = new StringBuilder();
        for (Class<?> aClass : classes) {
            if (buf.length() > 0) {
                buf.append(BatchCompilerCli.CLASS_SEPARATOR);
            }
            buf.append(aClass.getName());
        }
        return buf.toString();
    }

    private Predicate<? super Class<?>> predicate(Iterable<? extends Predicate<? super Class<?>>> elements) {
        Predicate<? super Class<?>> current = Predicates.anything();
        for (Predicate<? super Class<?>> p : elements) {
            current = Predicates.and(current, p);
        }
        return current;
    }

    private String files(File... files) {
        StringBuilder buf = new StringBuilder();
        for (File file : files) {
            if (buf.length() > 0) {
                buf.append(File.pathSeparatorChar);
            }
            buf.append(file.getPath());
        }
        return buf.toString();
    }

    private Matcher<? super Holder<?>> isEmpty() {
        return new FeatureMatcher<Holder<?>, Boolean>(is(true), "is empty", "isEmpty") {
            @Override
            protected Boolean featureValueOf(Holder<?> actual) {
                return actual.isEmpty();
            }
        };
    }

    private String[] strings(Object... values) {
        String[] results = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            results[i] = String.valueOf(values[i]);
        }
        return results;
    }
}