uk.co.unclealex.executable.generator.ScripterImplTest.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.unclealex.executable.generator.ScripterImplTest.java

Source

/**
 * Copyright 2012 Alex Jones
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.    
 *
 * @author unclealex72
 *
 */

package uk.co.unclealex.executable.generator;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.co.unclealex.executable.Executable;
import uk.co.unclealex.executable.generator.jar.JarService;
import uk.co.unclealex.executable.generator.scan.exception.ExecutableScanException;
import uk.co.unclealex.process.ProcessRequest;
import uk.co.unclealex.process.ProcessResult;
import uk.co.unclealex.process.builder.ProcessRequestBuilder;
import uk.co.unclealex.process.inject.ProcessRequestBuilderModule;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.lexicalscope.jewel.cli.CommandLineInterface;

/**
 * @author alex
 * 
 */
public class ScripterImplTest implements Opcodes {

    private static final Logger log = LoggerFactory.getLogger(ScripterImplTest.class);

    private Path tempDir;

    @Before
    public void setUp() throws IOException {
        tempDir = Files.createTempDirectory("scripterimpl-");
    }

    @Test
    public void testScript() throws IOException, URISyntaxException, ExecutableScanException {
        Path classesDir = tempDir.resolve("classes");
        Path scripterClassesDir = classesDir.resolve("scripter");
        final Path workDir = tempDir.resolve("work");
        Path homeDir = tempDir.resolve("home");
        for (Path dir : new Path[] { scripterClassesDir, workDir, homeDir }) {
            Files.createDirectories(dir);
        }
        generateClasses("First", "first", false, scripterClassesDir);
        generateClasses("Second", "second", true, scripterClassesDir);
        Injector injector = Guice.createInjector(new CodeGeneratorModule());
        Scripter scripter = injector.getInstance(Scripter.class);
        final JarService jarService = injector.getInstance(JarService.class);

        Path scriptPath = homeDir.resolve("script.sh");
        Function<Class<?>, Path> jarFunction = new Function<Class<?>, Path>() {
            @Override
            public Path apply(Class<?> clazz) {
                try {
                    return jarService.findJarFileOrDirectoryDefiningClass(getClass().getClassLoader(),
                            clazz.getName());
                } catch (IOException e) {
                    e.printStackTrace(System.err);
                    Assert.fail("Could not find a jar file for class " + clazz);
                    return null;
                }
            }
        };
        @SuppressWarnings("unchecked")
        List<Class<?>> classes = Lists.newArrayList(CommandLineInterface.class, Lists.class, Executable.class);
        Iterable<Path> jarFiles = Iterables.transform(classes, jarFunction);
        scripter.generate(scriptPath, classesDir, homeDir.toString(), "999", workDir, jarFiles);
        Assert.assertTrue("The generated script was not executable.", Files.isExecutable(scriptPath));
        checkRun(homeDir, scriptPath, "[One, Two, Three]\n", "", 0, "--runas", "first", "One", "Two", "Three");
        checkRun(homeDir, scriptPath, "[Three, Two, One]\n", "", 0, "--runas", "second", "One", "Two", "Three");
        checkRun(homeDir, scriptPath, "", "Exactly one of --links, --show or --runas must be specified.\n", 2);
    }

    protected void checkRun(Path homeDir, Path scriptPath, String expectedStdout, String expectedStderr,
            int expectedReturnValue, String... arguments) throws IOException {
        ProcessRequestBuilder processRequestBuilder = Guice.createInjector(new ProcessRequestBuilderModule())
                .getInstance(ProcessRequestBuilder.class);
        String command = scriptPath.toAbsolutePath().toString();
        final ProcessRequest processRequest = processRequestBuilder.forCommand(command).withArguments(arguments);
        Callable<ProcessResult> callable = new Callable<ProcessResult>() {
            @Override
            public ProcessResult call() throws IOException {
                return processRequest.execute();
            }
        };
        List<String> commandParts = Lists.newArrayList();
        commandParts.add(command);
        commandParts.addAll(Arrays.asList(arguments));
        String fullCommand = Joiner.on(' ').join(commandParts);
        try {
            ProcessResult processResult = Executors.newSingleThreadExecutor().submit(callable).get(10,
                    TimeUnit.SECONDS);
            Assert.assertTrue("The executable jar was not copied.",
                    Files.isReadable(homeDir.resolve(".executable").resolve("executable-999.jar")));
            Assert.assertEquals("Command " + fullCommand + " produced the wrong stderr.", expectedStderr,
                    processResult.getError());
            Assert.assertEquals("Command " + fullCommand + " produced the wrong stdout.", expectedStdout,
                    processResult.getOutput());
            Assert.assertEquals("Command " + fullCommand + " returned with the wrong value.", expectedReturnValue,
                    processResult.getReturnValue());
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            log.error("Running command " + fullCommand + " failed.", e);
            Assert.fail("Running command " + fullCommand + " failed.");
        }
    }

    protected void generateClasses(String classPrefixName, String commandName, boolean reverse,
            Path targetDirectory) throws IOException {
        generateCommandClass(classPrefixName, reverse, targetDirectory);
        generateCommandLineClass(classPrefixName, commandName, targetDirectory);
    }

    protected void generateCommandClass(String classPrefixName, boolean reverse, Path targetDirectory)
            throws IOException {
        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;

        cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "scripter/" + classPrefixName + "Command", null, "java/lang/Object",
                new String[] { "uk/co/unclealex/executable/streams/HasStdout" });
        fv = cw.visitField(ACC_PRIVATE, "stdout", "Ljava/io/PrintStream;", null, null);
        fv.visitEnd();
        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        mv = cw.visitMethod(ACC_PUBLIC, "run", "(Lscripter/" + classPrefixName + "CommandLine;)V", null, null);
        AnnotationVisitor av0 = mv.visitAnnotation("Luk/co/unclealex/executable/Executable;", true);
        av0.visitEnd();
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKEINTERFACE, "scripter/" + classPrefixName + "CommandLine", "getArguments",
                "()Ljava/util/List;");
        mv.visitMethodInsn(INVOKESTATIC, "com/google/common/collect/Lists", "newArrayList",
                "(Ljava/lang/Iterable;)Ljava/util/ArrayList;");
        mv.visitVarInsn(ASTORE, 2);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, "scripter/" + classPrefixName + "Command", "reverse", "()Z");
        Label l0 = new Label();
        mv.visitJumpInsn(IFEQ, l0);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESTATIC, "java/util/Collections", "reverse", "(Ljava/util/List;)V");
        mv.visitLabel(l0);
        mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { "java/util/List" }, 0, null);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, "scripter/" + classPrefixName + "Command", "stdout", "Ljava/io/PrintStream;");
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/Object;)V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 3);
        mv.visitEnd();
        mv = cw.visitMethod(ACC_PROTECTED, "reverse", "()Z", null, null);
        mv.visitCode();
        mv.visitInsn(reverse ? ICONST_1 : ICONST_0);
        mv.visitInsn(IRETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        mv = cw.visitMethod(ACC_PUBLIC, "getStdout", "()Ljava/io/PrintStream;", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, "scripter/" + classPrefixName + "Command", "stdout", "Ljava/io/PrintStream;");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        mv = cw.visitMethod(ACC_PUBLIC, "setStdout", "(Ljava/io/PrintStream;)V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitFieldInsn(PUTFIELD, "scripter/" + classPrefixName + "Command", "stdout", "Ljava/io/PrintStream;");
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
        cw.visitEnd();

        Files.write(targetDirectory.resolve(classPrefixName + "Command.class"), cw.toByteArray());
    }

    protected void generateCommandLineClass(String classPrefixName, String commandName, Path targetDirectory)
            throws IOException {
        ClassWriter cw = new ClassWriter(0);
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(V1_7, ACC_ABSTRACT + ACC_INTERFACE, "scripter/" + classPrefixName + "CommandLine", null,
                "java/lang/Object", null);

        av0 = cw.visitAnnotation("Lcom/lexicalscope/jewel/cli/CommandLineInterface;", true);
        av0.visit("application", commandName);
        av0.visitEnd();
        mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getArguments", "()Ljava/util/List;",
                "()Ljava/util/List<Ljava/lang/String;>;", null);
        av0 = mv.visitAnnotation("Lcom/lexicalscope/jewel/cli/Unparsed;", true);
        av0.visitEnd();
        mv.visitEnd();
        cw.visitEnd();

        Files.write(targetDirectory.resolve(classPrefixName + "CommandLine.class"), cw.toByteArray());
    }

    @After
    public void tearDown() throws IOException {
        FileUtils.deleteDirectory(tempDir.toFile());
    }
}