Package randoop.main

Class Minimize


  • public class Minimize
    extends CommandHandler
    This program minimizes a failing JUnit test suite. Its three command-line arguments are:
    1. the Java file whose failing tests will be minimized
    2. an optional classpath containing dependencies needed to compile and run the Java file
    3. an optional timeout limit, in seconds, allowed for the whole test suite to run. The default is 30 seconds.

    The minimizer will only attempt to minimize methods that are annotated with the @Test annotation. In a method that contains a failing assertion, the minimizer will iterate through the statements of the method, from last to first. For each statement, it tries possible replacement statements, from most minimized to least minimized. Removing the statement is the most a statement can be minimized. Leaving the statement unchanged is the least that the statement can be minimized.

    If a replacement causes the output test suite to fail differently than the original test suite, the algorithm tries a different replacement. If no replacement allows the output test suite to fail in the same way as the original test suite, the algorithm adds back the original version of the current statement and continues.

    • Constructor Summary

      Constructors 
      Constructor Description
      Minimize()
      Create the handler for Randoop's minimize command.
    • Method Summary

      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and Type Method Description
      private static void addImport​(com.github.javaparser.ast.CompilationUnit compilationUnit, String importName)
      Add an import to the list of ImportDeclarations of the compilation unit.
      private static boolean checkCorrectlyMinimized​(Path file, String classpath, String packageName, Map<String,​String> expectedOutput, int timeoutLimit)
      Check if a Java file has been correctly minimized.
      private static void cleanUp​(Path outputFile, boolean verboseOutput)
      Deletes the .class file associated with the outputFile.
      private static Minimize.Outputs compileJavaFile​(Path file, String classpath, String packageName, int timeoutLimit)
      Compile a Java file and return the compilation exit value.
      private static Path getExecutionDirectory​(Path file, String packageName)
      Get directory to execute command in, given file path and package name.
      private static int getFileLength​(Path file)
      Calculate the length of a file, by number of lines.
      private static com.github.javaparser.ast.expr.LiteralExpr getLiteralExpression​(String value, com.github.javaparser.ast.type.PrimitiveType.Primitive type)
      Return a literal expression with the value that is passed in.
      private static int getNumberOfTestMethods​(com.github.javaparser.ast.CompilationUnit compilationUnit)
      Return the number of JUnit test methods in a compilation unit.
      private static void getOrphanCommentsBeforeThisChildNode​(com.github.javaparser.ast.Node node, List<com.github.javaparser.ast.comments.Comment> result)
      This is stolen from JavaParser's PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode, with light modifications.
      private static List<com.github.javaparser.ast.stmt.Statement> getStatementReplacements​(com.github.javaparser.ast.stmt.Statement currStmt, Map<String,​String> primitiveValues)
      Return a list of statements that are a simplification of a given statement, in order from most to least minimized.
      boolean handle​(String[] args)
      Check that the required parameters have been specified by the command-line options and then call the mainMinimize method.
      private static boolean isTestMethod​(com.github.javaparser.ast.body.MethodDeclaration methodDeclaration)
      Check if the method is a JUnit test method.
      static boolean mainMinimize​(Path file, String classPath, int timeoutLimit, boolean verboseOutput)
      Minimize the input test file.
      static String minimizedClassName​(Path file)
      Given a .java filename for non-minimized tests, returns the simple name of the class containing the minimized tests.
      private static void minimizeMethod​(com.github.javaparser.ast.body.MethodDeclaration method, com.github.javaparser.ast.CompilationUnit compilationUnit, String packageName, Path file, String classpath, Map<String,​String> expectedOutput, int timeoutLimit)
      Minimize a method by minimizing each statement in turn.
      private static void minimizeTestSuite​(com.github.javaparser.ast.CompilationUnit compilationUnit, String packageName, Path file, String classpath, Map<String,​String> expectedOutput, int timeoutLimit)
      Visit and minimize every JUnit test method within a compilation unit.
      private static Map<String,​String> normalizeJUnitOutput​(String input)
      Normalize the standard output obtained from running a JUnit test suite.
      private static void primitiveVarEquality​(com.github.javaparser.ast.expr.Expression exp1, com.github.javaparser.ast.expr.Expression exp2, Map<String,​String> primitiveValues, Set<String> primitiveAndWrappedTypeVars)
      If one of the arguments is a NamxExpr and the other is a LiteralExpr, put them in primitiveValues.
      private static void printProgress​(int currentTestIndex, int totalTests, com.github.javaparser.ast.expr.SimpleName testName)
      Output the minimizer's current progress.
      private static com.github.javaparser.ast.stmt.Statement removeLeftHandSideSimplification​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr)
      Return a statement that contains only the right hand side of a given statement.
      private static com.github.javaparser.ast.stmt.Statement rhsAssignValueFromPassingAssertion​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr, Map<String,​String> primitiveValues)
      Return a variable declaration statement that simplifies the right hand side by a calculated value for primitive types.
      private static com.github.javaparser.ast.stmt.Statement rhsAssignWithValue​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr, com.github.javaparser.ast.type.Type exprType, String value)
      Return a variable declaration statement that sets the right hand side of a variable declaration to the value that is passed in.
      private static List<com.github.javaparser.ast.stmt.Statement> rhsAssignZeroValue​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr)
      Return a list of variable declaration statements that could replace the right hand side by 0, false, or null, whichever is type correct.
      private static String runJavaFile​(Path file, String userClassPath, String packageName, int timeoutLimit)
      Run a Java file and return the standard output.
      static Minimize.Outputs runProcess​(String command, Path executionDir, int timeoutLimit)
      Run a command given as a String and return the output and error results in an Outputs object.
      private static com.github.javaparser.ast.CompilationUnit simplifyTypeNames​(com.github.javaparser.ast.CompilationUnit compilationUnit, String packageName, Path file, String classpath, Map<String,​String> expectedOutput, int timeoutLimit, boolean verboseOutput)
      Simplify the type names in a compilation unit.
      private static void sortImports​(com.github.javaparser.ast.CompilationUnit compilationUnit)
      Sort a compilation unit's imports by name.
      private static void storeValueFromAssertion​(com.github.javaparser.ast.stmt.Statement currStmt, Map<String,​String> primitiveValues, Set<String> primitiveAndWrappedTypeVars)
      If currStmt is an assertion about a primitive value, store the value associated with the variable in the primitiveValues map.
      static void writeToFile​(com.github.javaparser.ast.CompilationUnit compilationUnit, Path file)
      Write a compilation unit to a Java file.
    • Field Detail

      • suitepath

        public static String suitepath
        The Java file whose failing tests will be minimized.
      • suiteclasspath

        public static String suiteclasspath
        Classpath that includes dependencies needed to compile and run the JUnit test suite being minimized.
      • minimizetimeout

        public static int minimizetimeout
        The maximum number of seconds allowed for the entire minimization process.
      • testsuitetimeout

        public static int testsuitetimeout
        The maximum number of seconds allowed for the entire test suite to run.
      • verboseminimizer

        public static boolean verboseminimizer
        Produce verbose diagnostics to standard output if true.
      • javaParser

        private static final com.github.javaparser.JavaParser javaParser
        An instance of a Java parser.
      • PATH_SEPARATOR

        private static final String PATH_SEPARATOR
        Path separator as defined by the system, used to separate elements of the classpath.
      • SUFFIX

        private static final String SUFFIX
        The suffix to postpend onto the name of the minimized file and class.
        See Also:
        Constant Field Values
    • Constructor Detail

      • Minimize

        Minimize()
        Create the handler for Randoop's minimize command.
    • Method Detail

      • minimizedClassName

        public static String minimizedClassName​(Path file)
        Given a .java filename for non-minimized tests, returns the simple name of the class containing the minimized tests.
        Parameters:
        file - the .java filename for non-minimized tests
        Returns:
        the simple class name for the minimized tests
      • handle

        public boolean handle​(String[] args)
        Check that the required parameters have been specified by the command-line options and then call the mainMinimize method.
        Specified by:
        handle in class CommandHandler
        Parameters:
        args - parameters, specified in command-line style, for the input file, the classpath, the timeout value, and the verbose flag
        Returns:
        true if the command was handled successfully
      • mainMinimize

        public static boolean mainMinimize​(Path file,
                                           String classPath,
                                           int timeoutLimit,
                                           boolean verboseOutput)
                                    throws IOException
        Minimize the input test file.

        Given an input Java file, minimization produces a smaller file that fails in the same way as the original, having the same failing assertions with the same stack trace.

        1. Same failing assertions as in the original input test suite.
        2. Same stacktrace produced by failing assertions.

        The original input Java file will be compiled and run once. The "expected output" derived from the standard output from running the input file is a map from test method name to failure stack trace. A method is included in the map only if the method contains a failing assertion. Thus, the "expected output" of running a test suite with no failing tests will be an empty map. The "expected output" will be used during subsequent runs of the modified test suite to determine whether or not the test suite still fails in the same way.

        Parameters:
        file - the Java file that is being minimized
        classPath - classpath used to compile and run the Java file
        timeoutLimit - number of seconds allowed for the whole test suite to run
        verboseOutput - whether to produce verbose output
        Returns:
        true if minimization produced a (possibly unchanged) file that fails the same way as the original file
        Throws:
        IOException - if write to file fails
      • minimizeTestSuite

        private static void minimizeTestSuite​(com.github.javaparser.ast.CompilationUnit compilationUnit,
                                              String packageName,
                                              Path file,
                                              String classpath,
                                              Map<String,​String> expectedOutput,
                                              int timeoutLimit)
                                       throws IOException
        Visit and minimize every JUnit test method within a compilation unit.
        Parameters:
        compilationUnit - the compilation unit to minimize; is modified by side effect
        packageName - the package that the Java file is in
        file - the Java file that is being minimized; is modified by side effect
        classpath - classpath used to compile and run the Java file
        expectedOutput - expected JUnit output when the Java file is compiled and run
        timeoutLimit - number of seconds allowed for the whole test suite to run
        Throws:
        IOException - thrown if minimized method can't be written to file
      • isTestMethod

        private static boolean isTestMethod​(com.github.javaparser.ast.body.MethodDeclaration methodDeclaration)
        Check if the method is a JUnit test method.
        Parameters:
        methodDeclaration - the method declaration to check
        Returns:
        true if the method is a JUnit test method
      • minimizeMethod

        private static void minimizeMethod​(com.github.javaparser.ast.body.MethodDeclaration method,
                                           com.github.javaparser.ast.CompilationUnit compilationUnit,
                                           String packageName,
                                           Path file,
                                           String classpath,
                                           Map<String,​String> expectedOutput,
                                           int timeoutLimit)
                                    throws IOException
        Minimize a method by minimizing each statement in turn.
        Parameters:
        method - the method to minimize; is modified by side effect
        compilationUnit - compilation unit for the Java file that we are minimizing; is modified by side effect
        packageName - the package that the Java file is in
        file - the Java file that is being minimized; is modified by side effect
        classpath - classpath needed to compile and run the Java file
        expectedOutput - expected output from running the JUnit test suite
        timeoutLimit - number of seconds allowed for the whole test suite to run
        Throws:
        IOException - thrown if write to file fails
      • storeValueFromAssertion

        private static void storeValueFromAssertion​(com.github.javaparser.ast.stmt.Statement currStmt,
                                                    Map<String,​String> primitiveValues,
                                                    Set<String> primitiveAndWrappedTypeVars)
        If currStmt is an assertion about a primitive value, store the value associated with the variable in the primitiveValues map.

        currStmt might be an assertTrue statement using an '==' operator, or an assertEquals statement.

        Parameters:
        currStmt - a statement
        primitiveValues - a map of variable names to variable values; modified if currStmt is a passing assertion, asserting a variable's value
        primitiveAndWrappedTypeVars - set containing the names of all primitive and wrapped type variables
      • primitiveVarEquality

        private static void primitiveVarEquality​(com.github.javaparser.ast.expr.Expression exp1,
                                                 com.github.javaparser.ast.expr.Expression exp2,
                                                 Map<String,​String> primitiveValues,
                                                 Set<String> primitiveAndWrappedTypeVars)
        If one of the arguments is a NamxExpr and the other is a LiteralExpr, put them in primitiveValues.
        Parameters:
        exp1 - the first expression
        exp2 - the second expression
        primitiveValues - a map of variable names to variable values; modified if currStmt is a passing assertion, asserting a variable's value
        primitiveAndWrappedTypeVars - set containing the names of all primitive and wrapped type variables
      • getStatementReplacements

        private static List<com.github.javaparser.ast.stmt.Statement> getStatementReplacements​(com.github.javaparser.ast.stmt.Statement currStmt,
                                                                                               Map<String,​String> primitiveValues)
        Return a list of statements that are a simplification of a given statement, in order from most to least minimized. The possible minimizations are:
        • Remove a statement, represented by null.
        • Replace the right hand side expression with 0, false, or null.
        • Replace right hand side by a calculated value obtained from a passing assertion.
        • Remove the left hand side of a statement, retaining only the expression on the right.

        Assertions are never simplified, only removed completely.

        Parameters:
        currStmt - statement to simplify
        primitiveValues - map of primitive variable names to expressions representing their values
        Returns:
        list of statements, where each is a possible simplification of currStmt
      • rhsAssignZeroValue

        private static List<com.github.javaparser.ast.stmt.Statement> rhsAssignZeroValue​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr)
        Return a list of variable declaration statements that could replace the right hand side by 0, false, or null, whichever is type correct. Returns an empty list if there are multiple variable declarations in a single statement, such as int i, j, k; .
        Parameters:
        vdExpr - variable declaration expression representing the current statement to simplify
        Returns:
        a list of Statement objects representing the simplified variable declaration expression
      • rhsAssignValueFromPassingAssertion

        private static com.github.javaparser.ast.stmt.Statement rhsAssignValueFromPassingAssertion​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr,
                                                                                                   Map<String,​String> primitiveValues)
        Return a variable declaration statement that simplifies the right hand side by a calculated value for primitive types. Returns null if there are multiple variable declarations in a single statement, such as int i, j, k; .
        Parameters:
        vdExpr - variable declaration expression representing the current statement to simplify
        primitiveValues - a map of primitive variable names to expressions representing their values
        Returns:
        a Statement object representing the simplified variable declaration expression if the type of the variable is a primitive and a value has been previously calculated. Otherwise, returns null. Also returns null if more than one variable is declared in the VariableDeclarationExpr.
      • rhsAssignWithValue

        private static com.github.javaparser.ast.stmt.Statement rhsAssignWithValue​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr,
                                                                                   com.github.javaparser.ast.type.Type exprType,
                                                                                   String value)
        Return a variable declaration statement that sets the right hand side of a variable declaration to the value that is passed in.
        Parameters:
        vdExpr - variable declaration expression representing the current statement to simplify
        exprType - type of the variable declaration expression, should not be null
        value - value that will be assigned to the variable being declared. If the value is null, then the right hand side will have the zero value of the variable declaration's type.
        Returns:
        a Statement object representing the simplified variable declaration expression. Returns null if more than one variable is declared in the VariableDeclarationExpr.
      • getLiteralExpression

        private static com.github.javaparser.ast.expr.LiteralExpr getLiteralExpression​(String value,
                                                                                       com.github.javaparser.ast.type.PrimitiveType.Primitive type)
        Return a literal expression with the value that is passed in.
        Parameters:
        value - the value for the literal expression. If null, the value of the literal expression will be the zero value for the type that is passed in.
        type - the type of the expression, needs to be one of the eight primitive types
        Returns:
        a literal expression containing the value that is passed in
      • removeLeftHandSideSimplification

        private static com.github.javaparser.ast.stmt.Statement removeLeftHandSideSimplification​(com.github.javaparser.ast.expr.VariableDeclarationExpr vdExpr)
        Return a statement that contains only the right hand side of a given statement. Returns null if there are multiple variable declarations in a single statement, such as int i=1, j=2, k=3; , or if there are no initializers, as in int i;.
        Parameters:
        vdExpr - variable declaration expression that represents the statement to simplify
        Returns:
        a Statement object that is equal to the right-hand-side of vdExpr. Returns null if more than one variable is declared in the VariableDeclarationExpr.
      • simplifyTypeNames

        private static com.github.javaparser.ast.CompilationUnit simplifyTypeNames​(com.github.javaparser.ast.CompilationUnit compilationUnit,
                                                                                   String packageName,
                                                                                   Path file,
                                                                                   String classpath,
                                                                                   Map<String,​String> expectedOutput,
                                                                                   int timeoutLimit,
                                                                                   boolean verboseOutput)
                                                                            throws IOException
        Simplify the type names in a compilation unit. For example, java.lang.String should be simplified to String. If two different types have the same simple type name, then the lexicographically first one is simplified and the other is left unchanged.

        Additionally, sort the import statements of the compilation unit.

        Parameters:
        compilationUnit - compilation unit containing an AST for a Java file, the compilation unit will be modified if a correct minimization of the method is found
        packageName - the package that the Java file is in
        file - the Java file to simplify; is modified by side effect
        classpath - classpath needed to compile and run the Java file
        expectedOutput - expected standard output from running the JUnit test suite
        timeoutLimit - number of seconds allowed for the whole test suite to run
        verboseOutput - whether or not to output information about minimization status
        Returns:
        CompilationUnit with fully-qualified type names simplified to simple type names
        Throws:
        IOException - thrown if write to file fails
      • checkCorrectlyMinimized

        private static boolean checkCorrectlyMinimized​(Path file,
                                                       String classpath,
                                                       String packageName,
                                                       Map<String,​String> expectedOutput,
                                                       int timeoutLimit)
        Check if a Java file has been correctly minimized. The file should not have compilation errors or run-time errors. The file should fail in the same way as the original file.
        Parameters:
        file - the file being checked
        classpath - classpath needed to compile/run the Java file
        packageName - the package that the Java file is in
        expectedOutput - expected output of running the JUnit test suite
        timeoutLimit - number of seconds allowed for the whole test suite to run
        Returns:
        true if there are no compilation and no run-time errors and the output is equal to the expected output
      • compileJavaFile

        private static Minimize.Outputs compileJavaFile​(Path file,
                                                        String classpath,
                                                        String packageName,
                                                        int timeoutLimit)
        Compile a Java file and return the compilation exit value.
        Parameters:
        file - the file to be compiled and executed
        classpath - dependencies and complete classpath to compile and run the Java program
        packageName - the package that the Java file is in
        timeoutLimit - number of seconds allowed for the whole test suite to run
        Returns:
        the result of compilation (includes status and output)
      • runJavaFile

        private static String runJavaFile​(Path file,
                                          String userClassPath,
                                          String packageName,
                                          int timeoutLimit)
        Run a Java file and return the standard output.
        Parameters:
        file - the file to be compiled and executed
        userClassPath - dependencies and complete classpath to compile and run the Java program
        packageName - the package that the Java file is in
        timeoutLimit - number of seconds allowed for the Java program to run
        Returns:
        standard output from running the Java file
      • getExecutionDirectory

        private static Path getExecutionDirectory​(Path file,
                                                  String packageName)
        Get directory to execute command in, given file path and package name. Returns a Path pointing to the directory that the Java file should be executed in.

        For the simplest case where the Java file is nested in a single package layer, i.e. MyJavaFile.java is in the package mypackage, the folder structure would be src/mypackage/MyJavaFile.java. Here, we need to execute the Java file in the src/ directory. We go up 2 layers from the directory of the Java file to get to the parent directory of the directory for the package. For the general case where MyJavaFile.java is nested within multiple layers of packages, we count the number of separators, i.e. ".", and add 2 to get the number of layers to go up from the Java file's directory.

        Parameters:
        file - the Java file to be executed
        packageName - package name of input Java file
        Returns:
        the directory to execute the commands in, or null if packageName is null
      • runProcess

        public static Minimize.Outputs runProcess​(String command,
                                                  Path executionDir,
                                                  int timeoutLimit)
        Run a command given as a String and return the output and error results in an Outputs object.
        Parameters:
        command - the input command to be run
        executionDir - the directory where the process commands should be executed
        timeoutLimit - number of seconds allowed for the command to run
        Returns:
        an Outputs object containing the standard and error output
      • normalizeJUnitOutput

        private static Map<String,​String> normalizeJUnitOutput​(String input)
        Normalize the standard output obtained from running a JUnit test suite. By normalizing the String representation of the output, we remove any extraneous information such as line numbers. The resulting output is a map from method name to the method's failure stack trace.
        Parameters:
        input - the String produced from running a JUnit test suite
        Returns:
        a map from method name to the method's failure stack trace. The stack trace will not contain any line numbers.
      • writeToFile

        public static void writeToFile​(com.github.javaparser.ast.CompilationUnit compilationUnit,
                                       Path file)
                                throws IOException
        Write a compilation unit to a Java file.
        Parameters:
        compilationUnit - the compilation unit to write to file
        file - file to write to
        Throws:
        IOException - thrown if write to file fails
      • addImport

        private static void addImport​(com.github.javaparser.ast.CompilationUnit compilationUnit,
                                      String importName)
        Add an import to the list of ImportDeclarations of the compilation unit. This method adds the import statement if it has not been yet included in the list of current imports of the compilation unit.
        Parameters:
        compilationUnit - compilation unit to add import to
        importName - the name of the import
      • sortImports

        private static void sortImports​(com.github.javaparser.ast.CompilationUnit compilationUnit)
        Sort a compilation unit's imports by name.
        Parameters:
        compilationUnit - the compilation unit whose imports will be sorted by name
      • getFileLength

        private static int getFileLength​(Path file)
                                  throws IOException
        Calculate the length of a file, by number of lines.
        Parameters:
        file - the file to compute the length of
        Returns:
        the number of lines in the file. Returns -1 if an exception occurs while reading the file.
        Throws:
        IOException - thrown if error reading file
      • cleanUp

        private static void cleanUp​(Path outputFile,
                                    boolean verboseOutput)
        Deletes the .class file associated with the outputFile.
        Parameters:
        outputFile - the source file for the class file to be removed
        verboseOutput - whether to print information about minimization status
      • getNumberOfTestMethods

        private static int getNumberOfTestMethods​(com.github.javaparser.ast.CompilationUnit compilationUnit)
        Return the number of JUnit test methods in a compilation unit.
        Parameters:
        compilationUnit - the compilation unit to count the number of unit test methods
        Returns:
        the number of unit test methods in compilationUnit
      • printProgress

        private static void printProgress​(int currentTestIndex,
                                          int totalTests,
                                          com.github.javaparser.ast.expr.SimpleName testName)
        Output the minimizer's current progress.
        Parameters:
        currentTestIndex - the number of tests that have been minimized so far
        totalTests - the total number of tests in the input test suite
        testName - the current test method being minimized
      • getOrphanCommentsBeforeThisChildNode

        private static void getOrphanCommentsBeforeThisChildNode​(com.github.javaparser.ast.Node node,
                                                                 List<com.github.javaparser.ast.comments.Comment> result)
        This is stolen from JavaParser's PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode, with light modifications.
        Parameters:
        node - the node whose orphan comments to collect
        result - the list to add orphan comments to. Is side-effected by this method. The implementation uses this to minimize the diffs against upstream.