diff --git a/.gitignore b/.gitignore index 5281f985..6cfd1225 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ gradle-app.setting ## Local properties local.properties +ant.log diff --git a/app/build.gradle b/app/build.gradle index eab27779..72e2764c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,12 +64,12 @@ task checkstyle(type: Checkstyle) { } android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.3' defaultConfig { applicationId "org.mate" minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -97,7 +97,7 @@ android { dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3' implementation 'com.android.support.test:runner:1.0.2' } diff --git a/app/src/androidTest/java/org/mate/ExecuteMATEAntColony.java b/app/src/androidTest/java/org/mate/ExecuteMATEAntColony.java new file mode 100644 index 00000000..1d527a27 --- /dev/null +++ b/app/src/androidTest/java/org/mate/ExecuteMATEAntColony.java @@ -0,0 +1,20 @@ +package org.mate; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ExecuteMATEAntColony { + + + @Test + public void useAppContext() throws Exception { + + MATE.log_acc("Starting Ant Colony Exploration..."); + + MATE mate = new MATE(); + mate.testApp("AntColonyExploration"); + } +} diff --git a/app/src/main/java/org/mate/MATE.java b/app/src/main/java/org/mate/MATE.java index 5e9313d7..0ecb7755 100644 --- a/app/src/main/java/org/mate/MATE.java +++ b/app/src/main/java/org/mate/MATE.java @@ -15,6 +15,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; +import org.mate.exploration.ant.AntColony; import org.mate.exploration.genetic.algorithm.RandomSearch; import org.mate.exploration.genetic.fitness.BranchDistanceFitnessFunction; import org.mate.exploration.genetic.fitness.BranchDistanceFitnessFunctionMultiObjective; @@ -49,6 +50,8 @@ import org.mate.exploration.genetic.fitness.TestLengthFitnessFunction; import org.mate.exploration.genetic.crossover.UniformSuiteCrossoverFunction; import org.mate.exploration.genetic.termination.NeverTerminationCondition; +import org.mate.exploration.genetic.termination.TargetLineCoveredTerminationCondition; +import org.mate.exploration.genetic.fitness.LineCoveredPercentageFitnessFunction; import org.mate.exploration.heuristical.HeuristicExploration; import org.mate.exploration.heuristical.RandomExploration; import org.mate.interaction.DeviceMgr; @@ -251,6 +254,16 @@ public Void call() throws Exception { .withTerminationCondition(IterTerminationCondition.TERMINATION_CONDITION_ID) .build(); nsga.run(); + } else if (explorationStrategy.equals("AntColonyExploration")) { + uiAbstractionLayer = new UIAbstractionLayer(deviceMgr, packageName); + + AntColony antColony = new AntColony(); + antColony.run(); + + if (Properties.STORE_COVERAGE()) { + Registry.getEnvironmentManager().storeCoverageData(antColony, null); + MATE.log_acc("Total coverage: " + Registry.getEnvironmentManager().getCombinedCoverage()); + } } else if (explorationStrategy.equals("PrimitiveStandardGeneticAlgorithm")) { uiAbstractionLayer = new UIAbstractionLayer(deviceMgr, packageName); MATE.log_acc("Activities"); @@ -286,20 +299,6 @@ public Void call() throws Exception { } } else if (explorationStrategy.equals("StandardGeneticAlgorithm")) { uiAbstractionLayer = new UIAbstractionLayer(deviceMgr, packageName); - MATE.log_acc("Activities"); - for (String s : Registry.getEnvironmentManager().getActivityNames()) { - MATE.log_acc("\t" + s); - } - - if (Properties.COVERAGE() == Coverage.BRANCH_COVERAGE) { - // init the CFG - boolean isInit = Registry.getEnvironmentManager().initCFG(); - - if (!isInit) { - MATE.log("Couldn't initialise CFG! Aborting."); - throw new IllegalStateException("Graph initialisation failed!"); - } - } final IGeneticAlgorithm genericGA = new GeneticAlgorithmBuilder() .withAlgorithm(StandardGeneticAlgorithm.ALGORITHM_NAME) @@ -307,21 +306,18 @@ public Void call() throws Exception { .withSelectionFunction(FitnessProportionateSelectionFunction.SELECTION_FUNCTION_ID) .withCrossoverFunction(TestCaseMergeCrossOverFunction.CROSSOVER_FUNCTION_ID) .withMutationFunction(CutPointMutationFunction.MUTATION_FUNCTION_ID) - .withFitnessFunction(BranchDistanceFitnessFunction.FITNESS_FUNCTION_ID) - .withTerminationCondition(NeverTerminationCondition.TERMINATION_CONDITION_ID) - .withPopulationSize(50) - .withBigPopulationSize(100) + .withFitnessFunction(LineCoveredPercentageFitnessFunction.FITNESS_FUNCTION_ID, + Properties.TARGET_LINE()) + .withTerminationCondition(TargetLineCoveredTerminationCondition.TERMINATION_CONDITION_ID) + .withPopulationSize(20) + .withBigPopulationSize(40) .withMaxNumEvents(50) .withPMutate(0.3) .withPCrossover(0.7) .build(); - TimeoutRun.timeoutRun(new Callable() { - @Override - public Void call() throws Exception { - genericGA.run(); - return null; - } - }, MATE.TIME_OUT); + + TargetLineCoveredTerminationCondition.INSTANCE.setGeneticAlgorithm(genericGA); + genericGA.run(); if (Properties.STORE_COVERAGE()) { if (Properties.COVERAGE() == Coverage.BRANCH_COVERAGE) { @@ -398,15 +394,8 @@ public Void call() throws Exception { MATE.log_acc("\t" + s); } - final RandomExploration randomExploration = new RandomExploration(50); - - TimeoutRun.timeoutRun(new Callable() { - @Override - public Void call() throws Exception { - randomExploration.run(); - return null; - } - }, MATE.TIME_OUT); + final RandomExploration randomExploration = new RandomExploration(Properties.STORE_COVERAGE(), true, 50); + randomExploration.run(); if (Properties.STORE_COVERAGE()) { Registry.getEnvironmentManager().storeCoverageData(randomExploration, null); diff --git a/app/src/main/java/org/mate/Properties.java b/app/src/main/java/org/mate/Properties.java index d8c40ea1..08db1308 100644 --- a/app/src/main/java/org/mate/Properties.java +++ b/app/src/main/java/org/mate/Properties.java @@ -125,6 +125,10 @@ public static Coverage COVERAGE() { return propertyOr(Coverage.LINE_COVERAGE); } + public static String TARGET_LINE() { + return propertyOr(null); + } + // Primitive actions or widget based actions? diff --git a/app/src/main/java/org/mate/exploration/ant/AntColony.java b/app/src/main/java/org/mate/exploration/ant/AntColony.java new file mode 100644 index 00000000..4a51a946 --- /dev/null +++ b/app/src/main/java/org/mate/exploration/ant/AntColony.java @@ -0,0 +1,401 @@ +package org.mate.exploration.ant; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import org.mate.MATE; +import org.mate.Properties; +import org.mate.Registry; +import org.mate.exploration.genetic.chromosome.Chromosome; +import org.mate.exploration.genetic.chromosome.IChromosome; +import org.mate.exploration.genetic.fitness.IFitnessFunction; +import org.mate.exploration.genetic.fitness.LineCoveredPercentageFitnessFunction; +import org.mate.interaction.UIAbstractionLayer; +import org.mate.model.TestCase; +import org.mate.ui.Action; +import org.mate.ui.Widget; +import org.mate.ui.WidgetAction; +import org.mate.utils.Coverage; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; + +public class AntColony { + private final UIAbstractionLayer uiAbstractionLayer; + private final AntStatsLogger antStatsLogger; + + private double currentPheromoneStandardValue; + private Map pheromones = new HashMap<>(); + private List> testCasesList = new ArrayList<>(); + + // Parameters to customize the ACO algorithm + private static final int generationAmount = 40; + private static final int generationSize = 10; + private static final int antPathLength = 50; + private static final double evaporationRate = 0.1; + private static final double standardDepositAmount = 1.0; + + /* Parameter to change the process of calculating the action probability + * True: Calculate the probability for a action by taking the pheromone value and + * multiplying it with the weight of the action (depending on the action type) + * False: Only use the pheromone value of the action as the probability + */ + private static final boolean includeActionTypeInTransition = false; + + /* Parameter to change the process of depositing pheromones. + * True: Rank all ants in a generation according to the fitness value of their testcase. + * Best ants get to deposit most pheromones, decreasing downwards and worst ants don“t + * deposit any pheromones + * False: Only the ant with the best fitness value in a generation gets to deposit pheromones + */ + private static final boolean depositPheromonesWithRanking = false; + + public AntColony() { + uiAbstractionLayer = MATE.uiAbstractionLayer; + + // Create a logger to store collected data during the run + antStatsLogger = new AntStatsLogger(); + } + + public void run() { + // Store the start time of the algorithm for later runtime calculation + long algorithmStartTime = System.currentTimeMillis(); + + //antStatsLogger.write("Start of Algorithm at " + startTime + ", "); + antStatsLogger.write("\"Algorithm_Type\";\"Generation\";\"Ant\";\"Fitness_Value\";" + + "\"Current_Coverage\";\"Combined_Coverage\";\"Runtime\"\n"); + + // Get the target line for ACO to generate a test for and initialise the fitness function + String targetLine = Properties.TARGET_LINE(); + IFitnessFunction lineCoveredPercentageFitnessFunction + = new LineCoveredPercentageFitnessFunction(targetLine); + + // Log the current target line for later identification of the test and the settings for this run + antStatsLogger.write("\"ant\";\"" + + "includeActionTypeInTransition:\";\"" + includeActionTypeInTransition + "\";\"" + + "depositPheromonesWithRanking:\";\"" + depositPheromonesWithRanking + + "\";\"-\";\"" + targetLine + "\"\n"); + + // Value to add the correct pheromone amount to unknown widget actions + currentPheromoneStandardValue = 1.0; + + // Loop to create multiple generations of ants + long generationStartTime, antStartTime; + outerLoop: for (int i = 0; i < generationAmount; i++) { + // Store the start time of the generation for later runtime calculation + generationStartTime = System.currentTimeMillis(); + + MATE.log_acc("Generation #" + (i + 1)); + + // Create ants and check if they reach the target line + for (int z = 0; z < generationSize; z++){ + // Store the start time of the current ant for later runtime calculation + antStartTime = System.currentTimeMillis(); + + MATE.log_acc("Ant #" + (z + 1)); + + // Log the start of the log file line for the relevant data + antStatsLogger.write("\"ant\";\"" + (i + 1) + "\";\"" + (z + 1) + "\";"); + + // Create an ant to traverse the app and wrap the generated testcase in a chromosome + IChromosome chromosome = new Chromosome<>(runAnt()); + + // Necessary lines to calculate the fitness value for the stored chromosome + Registry.getEnvironmentManager().storeCoverageData(chromosome, null); + LineCoveredPercentageFitnessFunction.retrieveFitnessValues(chromosome); + + // Retrieve the relevant test case data + double fitnessValue = lineCoveredPercentageFitnessFunction.getFitness(chromosome); + double coverage = Registry.getEnvironmentManager().getCoverage(chromosome); + double combinedCoverage = Registry.getEnvironmentManager().getCombinedCoverage(); + + // Log the relevant test case data + antStatsLogger.write("\"" + fitnessValue + "\";\"" + coverage + "\";\"" + + combinedCoverage + "\";\""); + logCurrentRuntime(antStartTime); + + // Stop algorithm if target line was reached and log the runtime + result + if (fitnessValue == 1) { + antStatsLogger.write("\"ant\";\"" + (i + 1) + "\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(generationStartTime); + + MATE.log_acc("ACO finished successfully"); + antStatsLogger.write("\"ant\";\"-\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(algorithmStartTime); + + antStatsLogger.write("\"ant\";\"-\";\"-\";\"-\";\"-\";\"-\";\"successful\"\n"); + + break outerLoop; + } else { + // Add testcase of current ant to list for later depositing of pheromones + testCasesList.add(chromosome); + } + } + + // Pheromone evaporation + for (Map.Entry entry : pheromones.entrySet()) { + entry.setValue((1-evaporationRate)*entry.getValue()); + } + currentPheromoneStandardValue *= (1-evaporationRate); + + // Deposit pheromones + if (depositPheromonesWithRanking) { + // Map test cases to their fitness values in a sorted treemap + Map> sortedFitnessValues = new TreeMap<>(); + for (int y = 0; y < testCasesList.size(); y++) { + // Retrieve fitness value for current ant + double fitnessValue = + lineCoveredPercentageFitnessFunction.getFitness(testCasesList.get(y)); + + // Check if previous ant had same fitness value + if (sortedFitnessValues.containsKey(fitnessValue)) { + // Add current testcase to the list of testcases with that fitness value + sortedFitnessValues.get(fitnessValue).add(testCasesList.get(y).getValue()); + } else { + // Create a new map entry and add the current testcase + List tempList = new ArrayList<>(); + sortedFitnessValues.put(fitnessValue, tempList); + sortedFitnessValues.get(fitnessValue).add(testCasesList.get(y).getValue()); + } + } + + // Deposit pheromones for the better half of test cases. Amount steadily decreases + double depositAmount = standardDepositAmount; + double reductionAmount = 1 / (testCasesList.size() / 2.0); + int antsAllowedToDeposit = (int) Math.ceil(testCasesList.size() / 2.0); + for (Map.Entry> entry : sortedFitnessValues.entrySet()) { + // Check if all relevant ants have already deposited their pheromones + if(antsAllowedToDeposit > 0) { + for (TestCase currentTestCase : entry.getValue()) { + // Store the used actions of the current testcase without duplicates + List actionList = new ArrayList<>(); + List eventSequence = (List)(List) + currentTestCase.getEventSequence(); + for (int y = 0; y < eventSequence.size(); y++) { + if (!actionList.contains(eventSequence.get(y))) { + actionList.add(eventSequence.get(y)); + } + } + + // Deposit pheromones for all actions used in the current testcase + for (int y = 0; y < actionList.size(); y++) { + double newValue = pheromones.get(actionList.get(y)) + depositAmount; + pheromones.put(actionList.get(y), newValue); + } + + // Updating of iteration variables + depositAmount -= reductionAmount; + antsAllowedToDeposit--; + } + } else { + break; + } + } + + } else { + // Set the cache to the testcase of the first ant in the generation + IChromosome bestTestCase = testCasesList.get(0); + + // Compare each of the test cases of all ants in the generation with the current + // best one and store the best of both in the cache + for (int y = 1; y < testCasesList.size(); y++) { + if (lineCoveredPercentageFitnessFunction.getFitness(testCasesList.get(y)) > + lineCoveredPercentageFitnessFunction.getFitness(bestTestCase)) { + bestTestCase = testCasesList.get(y); + } + } + + // Store the used actions of the best testcase without duplicates + List actionList = new ArrayList<>(); + List eventSequence = + (List)(List) bestTestCase.getValue().getEventSequence(); + for (int y = 0; y < eventSequence.size(); y++) { + if (!actionList.contains(eventSequence.get(y))) { + actionList.add(eventSequence.get(y)); + } + } + + // Deposit pheromones for all actions used in the best testcase + for (int y = 0; y < actionList.size(); y++) { + double newValue = pheromones.get(actionList.get(y)) + standardDepositAmount; + pheromones.put(actionList.get(y), newValue); + } + } + + // Log the runtime of the current generation + antStatsLogger.write("\"ant\";\"" + (i + 1) + "\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(generationStartTime); + + // Check if the max amount of generations has been reached and log the result if so + if ((i + 1) == generationAmount) { + antStatsLogger.write("\"ant\";\"-\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(algorithmStartTime); + + antStatsLogger.write("\"ant\";\"-\";\"-\";\"-\";\"-\";\"-\";\"unsuccessful\"\n"); + } + } + + // Close the logger + antStatsLogger.close(); + } + + /** + * Method to create ants that run through the app to create a testcase + * @return the generated testcase + */ + private TestCase runAnt() { + // Reset the current App to guarantee standardized testing starting at the same state + MATE.uiAbstractionLayer.resetApp(); + + // Initialise probabilities and create a new testcase for the current ant + Map probabilities = new HashMap<>(); + TestCase testCase = TestCase.newInitializedTestCase(); + + // Start the loop to traverse the app with antPathLength-many steps + for (int i = 0; i < antPathLength; i++) { + // Get list possible actions to execute + List executableActions = uiAbstractionLayer.getExecutableActions(); + + // Variable to flag a premature shutdown due to ant executing closing action + boolean prematureShutdown; + WidgetAction currentAction = null; + + // If there is no executable action stop MATE and throw exception + if (executableActions.size() == 0) { + throw new IllegalStateException("No possible Transitions available! App contains" + + "a dead end."); + + // If there is only one possible widget action execute that one + } else if (executableActions.size() == 1) { + // Set pheromone value for the action if it does not already have one + if (!pheromones.containsKey(executableActions.get(0))) { + pheromones.put(executableActions.get(0), currentPheromoneStandardValue); + } + + // Execute the widget action and update the testcase + prematureShutdown = !testCase.updateTestCase(executableActions.get(0), "" + i); + currentAction = executableActions.get(0); + } else { + // Set pheromone values for the available widget actions without one + for (WidgetAction action : executableActions) { + if (!pheromones.containsKey(action)) { + pheromones.put(action, currentPheromoneStandardValue); + } + } + + // Store probabilities for each action with or without factoring in the action type + if(includeActionTypeInTransition) { + // Calculate attractiveness for all available actions and store the results + for (WidgetAction action : executableActions) { + // Get pheromone value for the current action + double pheromoneValue = pheromones.get(action); + + // Get the weight for the current action type + double actionTypeWeight = getActionTypeWeight(action); + + // Calculate attractiveness for the current action (pheromone * action type) + double probability = (pheromoneValue*actionTypeWeight); + + // Store the calculated value for the current action + probabilities.put(action, probability); + } + } else { + // Store pheromone value of all actions as their probability + for (WidgetAction action : executableActions) { + probabilities.put(action, pheromones.get(action)); + } + } + + // Sum up all the probabilities + double sumProbabilities = 0.0; + for (Map.Entry entry : probabilities.entrySet()) { + sumProbabilities += entry.getValue(); + } + + // Calculate relative probability for each option + for (Map.Entry entry : probabilities.entrySet()) { + entry.setValue(entry.getValue() / sumProbabilities); + } + + // Determine the next action with roulette and make the step + double randomValue = Math.random(); + double sum = 0.0; + for (Map.Entry entry : probabilities.entrySet()) { + sum += entry.getValue(); + currentAction = entry.getKey(); + if (sum > randomValue) { + break; + } + } + // Execute the widget action and update the testcase + prematureShutdown = !testCase.updateTestCase(currentAction, "" + i); + } + + // If the application gets shutdown the ant run is terminated and the action resulting + // in the shutdown gets set to 0 to not be used in future runs + if (prematureShutdown) { + pheromones.put(currentAction, 0.0); + break; + } + + // Reset used variables + probabilities.clear(); + } + return testCase; + } + + + /** + * Determine the weight for an action depending on the type of the action + * @param action the action to get a weight value for + * @return the determined value + */ + private Double getActionTypeWeight (WidgetAction action) { + double eventTypeWeight; + switch (action.getActionType()) { + case SWIPE_UP: + case SWIPE_DOWN: + case SWIPE_LEFT: + case SWIPE_RIGHT: + case BACK: + eventTypeWeight = 0.5; + break; + case MENU: + eventTypeWeight = 2; + break; + default: + eventTypeWeight = 1; + break; + } + return eventTypeWeight; + } + + /** + * Log the time past from a certain point in time until now + * @param startTime the start point to calculate the time difference from + */ + private void logCurrentRuntime (long startTime) { + // Get the current time + long currentTime = System.currentTimeMillis(); + + // Calculate the time difference in seconds + long secondsPast = (currentTime - startTime)/(1000); + + // Log the calculated time in the file + antStatsLogger.write(secondsPast + "\"\n"); + } +} diff --git a/app/src/main/java/org/mate/exploration/ant/AntStatsLogger.java b/app/src/main/java/org/mate/exploration/ant/AntStatsLogger.java new file mode 100644 index 00000000..cb6d6d0a --- /dev/null +++ b/app/src/main/java/org/mate/exploration/ant/AntStatsLogger.java @@ -0,0 +1,43 @@ +package org.mate.exploration.ant; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; + +public class AntStatsLogger { + private FileOutputStream fos; + private BufferedWriter writer; + + public AntStatsLogger() { + try { + fos = InstrumentationRegistry.getTargetContext().openFileOutput("antstats.log", Context.MODE_PRIVATE); + } catch (FileNotFoundException e) { + //Fehlerbehandlung + } + writer = new BufferedWriter(new OutputStreamWriter(fos)); + } + + public void write(String log) { + try { + writer.write(log); + writer.flush(); + } catch (IOException e) { + //Fehlerbehandlung + } + } + + public void close() { + try { + writer.close(); + fos.close(); + } catch (IOException e) { + //Fehlerbehandlung + throw new IllegalStateException("Error while closing ant stats logger", e); + } + } +} diff --git a/app/src/main/java/org/mate/exploration/genetic/builder/GeneticAlgorithmProvider.java b/app/src/main/java/org/mate/exploration/genetic/builder/GeneticAlgorithmProvider.java index 24af1747..3985e19a 100644 --- a/app/src/main/java/org/mate/exploration/genetic/builder/GeneticAlgorithmProvider.java +++ b/app/src/main/java/org/mate/exploration/genetic/builder/GeneticAlgorithmProvider.java @@ -42,6 +42,7 @@ import org.mate.exploration.genetic.termination.ITerminationCondition; import org.mate.exploration.genetic.termination.IterTerminationCondition; import org.mate.exploration.genetic.termination.NeverTerminationCondition; +import org.mate.exploration.genetic.termination.TargetLineCoveredTerminationCondition; import org.mate.model.TestCase; import java.util.ArrayList; @@ -362,6 +363,9 @@ private ITerminationCondition initializeTerminationCondition() { return new NeverTerminationCondition(); case ConditionalTerminationCondition.TERMINATION_CONDITION_ID: return new ConditionalTerminationCondition(); + case TargetLineCoveredTerminationCondition.TERMINATION_CONDITION_ID: + return new TargetLineCoveredTerminationCondition(); + default: throw new UnsupportedOperationException("Unknown termination condition: " + terminationConditionId); diff --git a/app/src/main/java/org/mate/exploration/genetic/core/GeneticAlgorithm.java b/app/src/main/java/org/mate/exploration/genetic/core/GeneticAlgorithm.java index 37b89067..d0cbdea6 100644 --- a/app/src/main/java/org/mate/exploration/genetic/core/GeneticAlgorithm.java +++ b/app/src/main/java/org/mate/exploration/genetic/core/GeneticAlgorithm.java @@ -3,13 +3,16 @@ import org.mate.MATE; import org.mate.Properties; import org.mate.Registry; +import org.mate.exploration.ant.AntStatsLogger; import org.mate.exploration.genetic.chromosome.IChromosome; import org.mate.exploration.genetic.chromosome_factory.IChromosomeFactory; import org.mate.exploration.genetic.crossover.ICrossOverFunction; import org.mate.exploration.genetic.fitness.IFitnessFunction; +import org.mate.exploration.genetic.fitness.LineCoveredPercentageFitnessFunction; import org.mate.exploration.genetic.mutation.IMutationFunction; import org.mate.exploration.genetic.selection.ISelectionFunction; import org.mate.exploration.genetic.termination.ITerminationCondition; +import org.mate.model.TestCase; import org.mate.ui.EnvironmentManager; import org.mate.utils.Coverage; import org.mate.utils.Randomness; @@ -17,6 +20,9 @@ import java.util.ArrayList; import java.util.List; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + /** * Abstract class that serves as a basis for genetic algorithms * @param Type wrapped by the chromosome implementation @@ -36,6 +42,8 @@ public abstract class GeneticAlgorithm implements IGeneticAlgorithm { protected double pCrossover; protected double pMutate; + private final AntStatsLogger antStatsLogger; + private Boolean algorithmFinished = FALSE; /** * Initializing the genetic algorithm with all necessary attributes @@ -65,15 +73,57 @@ public GeneticAlgorithm(IChromosomeFactory chromosomeFactory, ISelectionFunct this.pCrossover = pCrossover; this.pMutate = pMutate; + // Create a logger to store collected data during the run + antStatsLogger = new AntStatsLogger(); + currentGenerationNumber = 0; } @Override public void run() { + // Store the start time of the algorithm for later runtime calculation + long algorithmStartTime = System.currentTimeMillis(); + + // Add the column headlines to the log file + antStatsLogger.write("\"Algorithm_Type\";\"Generation_Number\";\"Population_Number\"" + + ";\"Fitness_Value\";\"Current_Coverage\";\"Combined_Coverage\";\"Runtime\"\n"); + createInitialPopulation(); - while (!terminationCondition.isMet()) { - evolve(); + + boolean successful = TRUE; + int iterations = 1; + + // Run the algorithm unit the max amount of test cases was executed + if(!algorithmFinished) { + while (!terminationCondition.isMet()) { + if (iterations < 20) { + evolve(); + + // Exit the loop if a successful test case was found + if(algorithmFinished) { + break; + } + iterations++; + } else { + successful = FALSE; + break; + } + } + } + + // Log the runtime of the algorithm + antStatsLogger.write("\"genetic\";\"-\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(algorithmStartTime); + + // Log the final result + if(successful) { + antStatsLogger.write("\"genetic\";\"-\";\"-\";\"-\";\"-\";\"-\";\"successful\""); + } else { + antStatsLogger.write("\"genetic\";\"-\";\"-\";\"-\";\"-\";\"-\";\"unsuccessful\""); } + + // Close the logger + antStatsLogger.close(); } @Override @@ -83,21 +133,80 @@ public List> getCurrentPopulation() { @Override public void createInitialPopulation() { + // Store the start time of the generation for later runtime calculation + long generationStartTime = System.currentTimeMillis(); MATE.log_acc("Creating initial population (1st generation)"); + + // Get the target line to generate a test for and initialise the fitness function + String targetLine = Properties.TARGET_LINE(); + IFitnessFunction lineCoveredPercentageFitnessFunction + = new LineCoveredPercentageFitnessFunction(targetLine); + + // Log the current target line for later identification of the test + antStatsLogger.write("\"genetic\";\"-\";\"-\";\"-\";\"-\";\"-\";\"" + targetLine + "\"\n"); + for (int i = 0; i < populationSize; i++) { - population.add(chromosomeFactory.createChromosome()); + // Store the start time of the population for later runtime calculation + long populationStartTime = System.currentTimeMillis(); + + IChromosome chromosomeT = chromosomeFactory.createChromosome(); + IChromosome chromosome = (IChromosome) chromosomeT; + + LineCoveredPercentageFitnessFunction.retrieveFitnessValues(chromosome); + + // Retrieve the relevant test case data + double fitnessValue = lineCoveredPercentageFitnessFunction.getFitness(chromosome); + double coverage = Registry.getEnvironmentManager().getCoverage(chromosome); + double combinedCoverage = Registry.getEnvironmentManager().getCombinedCoverage(); + + population.add(chromosomeT); + + MATE.log_acc("Fitness Value: " + fitnessValue); + + // Log the relevant test case data + antStatsLogger.write("\"genetic\";\"" + (currentGenerationNumber + 1) + "\";\"" + + (i + 1) + "\";\"" + fitnessValue + "\";\"" + coverage + "\";\"" + + combinedCoverage + "\";\""); + logCurrentRuntime(populationStartTime); + + // Check if the current generated test case was successful + if(fitnessValue == 1.0) { + algorithmFinished = TRUE; + break; + } + + MATE.log_acc("Algorithm Finished: " + algorithmFinished); } + // Log the runtime for the current generation + antStatsLogger.write("\"genetic\";\"" + (currentGenerationNumber + 1) + "\";\"-\";\"-\";" + + "\"-\";\"-\";\""); + logCurrentRuntime(generationStartTime); + logCurrentFitness(); currentGenerationNumber++; } @Override public void evolve() { + // Store the start time of the generation for later runtime calculation + long generationStartTime = System.currentTimeMillis(); + + // Initialise a variable to keep track of the current test case number + int testCaseCount = 0; + + // Get the target line to generate a test for and initialise the fitness function + String targetLine = Properties.TARGET_LINE(); + IFitnessFunction lineCoveredPercentageFitnessFunction + = new LineCoveredPercentageFitnessFunction(targetLine); + MATE.log_acc("Creating population #" + (currentGenerationNumber + 1)); List> newGeneration = new ArrayList<>(population); - while (newGeneration.size() < bigPopulationSize) { + outerLoop: while (newGeneration.size() < bigPopulationSize) { + // Store the start time for the current test case + long testCaseStartTime = System.currentTimeMillis(); + List> parents = selectionFunction.select(population, fitnessFunctions); IChromosome parent; @@ -114,7 +223,6 @@ public void evolve() { offspring = mutationFunction.mutate(parent); } - for (IChromosome chromosome : offspring) { if (newGeneration.size() == bigPopulationSize) { break; @@ -122,6 +230,26 @@ public void evolve() { newGeneration.add(chromosome); + testCaseCount++; + + IChromosome iChromosome = (IChromosome) chromosome; + + // Retrieve the relevant test case data + double fitnessValue = lineCoveredPercentageFitnessFunction.getFitness(iChromosome); + double coverage = Registry.getEnvironmentManager().getCoverage(iChromosome); + double combinedCoverage = Registry.getEnvironmentManager().getCombinedCoverage(); + + // Log the relevant test case data + antStatsLogger.write("\"genetic\";\"" + (currentGenerationNumber + 1) + "\";\"" + + testCaseCount + "\";\"" + fitnessValue + "\";\"" + coverage + "\";\"" + + combinedCoverage + "\";\""); + logCurrentRuntime(testCaseStartTime); + + // Check if the current generated test case was successful + if(fitnessValue == 1.0) { + algorithmFinished = TRUE; + break outerLoop; + } } } @@ -131,6 +259,13 @@ public void evolve() { List> tmp = getGenerationSurvivors(); population.clear(); population.addAll(tmp); + // TODO log new generation infos + + // Log the runtime for the current generation + antStatsLogger.write("\"genetic\";\"" + (currentGenerationNumber + 1) + "\";\"-\";" + + "\"-\";\"-\";\"-\";\""); + logCurrentRuntime(generationStartTime); + logCurrentFitness(); currentGenerationNumber++; } @@ -167,4 +302,18 @@ protected void logCurrentFitness() { } } -} + /** + * Log the time past from a certain point in time until now + * @param startTime the start point to calculate the time difference from + */ + private void logCurrentRuntime (long startTime) { + // Get the current time + long currentTime = System.currentTimeMillis(); + + // Calculate the time difference in seconds + long secondsPast = (currentTime - startTime)/(1000); + + // Log the calculated time in the file + antStatsLogger.write(secondsPast + "\"\n"); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mate/exploration/genetic/termination/TargetLineCoveredTerminationCondition.java b/app/src/main/java/org/mate/exploration/genetic/termination/TargetLineCoveredTerminationCondition.java new file mode 100644 index 00000000..42d7c7b9 --- /dev/null +++ b/app/src/main/java/org/mate/exploration/genetic/termination/TargetLineCoveredTerminationCondition.java @@ -0,0 +1,34 @@ +package org.mate.exploration.genetic.termination; + +import org.mate.Properties; +import org.mate.exploration.genetic.chromosome.IChromosome; +import org.mate.exploration.genetic.core.IGeneticAlgorithm; +import org.mate.exploration.genetic.fitness.LineCoveredPercentageFitnessFunction; +import org.mate.model.TestCase; + +public class TargetLineCoveredTerminationCondition implements ITerminationCondition { + public static final String TERMINATION_CONDITION_ID = "target_line_covered_termination_condition"; + + public static TargetLineCoveredTerminationCondition INSTANCE = null; + private LineCoveredPercentageFitnessFunction lineCoveredPercentageFitnessFunction; + private IGeneticAlgorithm geneticAlgorithm; + + public TargetLineCoveredTerminationCondition() { + INSTANCE = this; + lineCoveredPercentageFitnessFunction = new LineCoveredPercentageFitnessFunction(Properties.TARGET_LINE()); + } + + public void setGeneticAlgorithm(IGeneticAlgorithm geneticAlgorithm) { + this.geneticAlgorithm = geneticAlgorithm; + } + + @Override + public boolean isMet() { + for (IChromosome chromosome : geneticAlgorithm.getCurrentPopulation()) { + if (lineCoveredPercentageFitnessFunction.getFitness(chromosome) == 1.0) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mate/exploration/heuristical/RandomExploration.java b/app/src/main/java/org/mate/exploration/heuristical/RandomExploration.java index 094f699f..0648c572 100644 --- a/app/src/main/java/org/mate/exploration/heuristical/RandomExploration.java +++ b/app/src/main/java/org/mate/exploration/heuristical/RandomExploration.java @@ -2,10 +2,18 @@ import org.mate.MATE; import org.mate.Properties; +import org.mate.Registry; +import org.mate.exploration.ant.AntStatsLogger; +import org.mate.exploration.genetic.chromosome.Chromosome; +import org.mate.exploration.genetic.chromosome.IChromosome; import org.mate.exploration.genetic.chromosome_factory.AndroidRandomChromosomeFactory; +import org.mate.exploration.genetic.fitness.IFitnessFunction; +import org.mate.exploration.genetic.fitness.LineCoveredPercentageFitnessFunction; +import org.mate.model.TestCase; public class RandomExploration { private final AndroidRandomChromosomeFactory randomChromosomeFactory; + private final AntStatsLogger antStatsLogger; private final boolean alwaysReset; public RandomExploration(int maxNumEvents) { @@ -15,18 +23,103 @@ public RandomExploration(int maxNumEvents) { public RandomExploration(boolean storeCoverage, boolean alwaysReset, int maxNumEvents) { this.alwaysReset = alwaysReset; randomChromosomeFactory = new AndroidRandomChromosomeFactory(storeCoverage, alwaysReset, maxNumEvents); + + // Create a logger to store collected data during the run + antStatsLogger = new AntStatsLogger(); } public void run() { + // Store the start time of the algorithm for later runtime calculation + long algorithmStartTime = System.currentTimeMillis(); + + // Maximum amount of test cases equal to ants in aco, generationAmount * generationSize + int antAmount = 40 * 10; + + // Add the column headlines to the log file + antStatsLogger.write("\"Algorithm_Type\";\"Test_Case\";\"Fitness_Value\";" + + "\"Current_Coverage\";\"Combined_Coverage\";\"Runtime\"\n"); + + // Get the target line to generate a test for and initialise the fitness function + String targetLine = Properties.TARGET_LINE(); + IFitnessFunction lineCoveredPercentageFitnessFunction + = new LineCoveredPercentageFitnessFunction(targetLine); + + // Log the current target line for later identification of the test + antStatsLogger.write("\"random\";\"-\";\"-\";\"-\";\"-\";\"" + targetLine + "\"\n"); + if (!alwaysReset) { MATE.uiAbstractionLayer.resetApp(); } + + // Declare a variable for the storing of the start time for each test case + long testCaseStartTime; + + // Loop to create the individual test cases and log collected information during them for (int i = 0; true; i++) { MATE.log_acc("Exploration #" + (i + 1)); - randomChromosomeFactory.createChromosome(); + + // Store the start time for the current test case + testCaseStartTime = System.currentTimeMillis(); + + // Store the chromosome created in each step to calculate fitness value + IChromosome chromosome = randomChromosomeFactory.createChromosome(); + + if (!alwaysReset) { + MATE.uiAbstractionLayer.restartApp(); + } + + // Retrieve the relevant test case data + double fitnessValue = lineCoveredPercentageFitnessFunction.getFitness(chromosome); + double coverage = Registry.getEnvironmentManager().getCoverage(chromosome); + double combinedCoverage = Registry.getEnvironmentManager().getCombinedCoverage(); + + // Log the relevant test case data + antStatsLogger.write("\"random\";\"" + (i + 1) + "\";\"" + fitnessValue + "\";\"" + + coverage + "\";\"" + combinedCoverage + "\";\""); + logCurrentRuntime(testCaseStartTime); + + // Stop algorithm if the target line or if the max amount of test cases is reached + if (fitnessValue == 1) { + MATE.log_acc("Random Exploration finished successfully"); + + // Log algorithm runtime and results into the file + antStatsLogger.write("\"random\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(algorithmStartTime); + antStatsLogger.write("\"random\";\"-\";\"-\";\"-\";\"-\";\"successful\"\n"); + + break; + } else if (i == (antAmount - 1)) { + MATE.log_acc("Random Exploration finished unsuccessfully"); + + // Log algorithm runtime and results into the file + antStatsLogger.write("\"random\";\"-\";\"-\";\"-\";\"-\";\""); + logCurrentRuntime(algorithmStartTime); + antStatsLogger.write("\"random\";\"-\";\"-\";\"-\";\"-\";\"unsuccessful\"\n"); + + break; + } + if (!alwaysReset) { MATE.uiAbstractionLayer.restartApp(); } } + + // Close the logger + antStatsLogger.close(); + } + + /** + * Log the time past from a certain point in time until now + * @param startTime the start point to calculate the time difference from + */ + private void logCurrentRuntime (long startTime) { + // Get the current time + long currentTime = System.currentTimeMillis(); + + // Calculate the time difference in seconds + long secondsPast = (currentTime - startTime)/(1000); + + // Log the calculated time in the file + antStatsLogger.write(secondsPast + "\"\n"); } } diff --git a/build.gradle b/build.gradle index 2dafc921..7bc93d63 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.2' + classpath 'com.android.tools.build:gradle:3.5.3' classpath 'gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.7.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index aac7c9b4..27b14e86 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx768m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit