/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * 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 org.optaplanner.core.impl.score.director.drools.testgen; import java.util.ArrayList; import java.util.List; import org.optaplanner.core.impl.score.director.drools.testgen.fact.TestGenFact; import org.optaplanner.core.impl.score.director.drools.testgen.mutation.TestGenHeadCuttingMutator; import org.optaplanner.core.impl.score.director.drools.testgen.mutation.TestGenRemoveRandomBlockMutator; import org.optaplanner.core.impl.score.director.drools.testgen.operation.TestGenKieSessionInsert; import org.optaplanner.core.impl.score.director.drools.testgen.operation.TestGenKieSessionOperation; import org.optaplanner.core.impl.score.director.drools.testgen.operation.TestGenKieSessionUpdate; import org.optaplanner.core.impl.score.director.drools.testgen.reproducer.TestGenOriginalProblemReproducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class TestGenerator { private static final Logger logger = LoggerFactory.getLogger(TestGenerator.class); private final TestGenOriginalProblemReproducer reproducer; private TestGenKieSessionJournal journal; static TestGenKieSessionJournal minimize(TestGenKieSessionJournal journal, TestGenOriginalProblemReproducer reproducer) { return new TestGenerator(journal, reproducer).run(); } private TestGenerator(TestGenKieSessionJournal journal, TestGenOriginalProblemReproducer reproducer) { this.journal = journal; this.reproducer = reproducer; } private TestGenKieSessionJournal run() { logger.info("Creating a minimal test that reproduces following Drools problem: {}", reproducer); logger.info("The KIE session journal has {} facts, {} inserts and {} updates.", journal.getFacts().size(), journal.getInitialInserts().size(), journal.getMoveOperations().size()); logger.info("Trying to reproduce with the complete KIE session journal..."); assertOriginalExceptionReproduced("Cannot reproduce the original problem even without journal modifications. " + "This is a bug!"); logger.info("Reproduced."); dropOldestUpdates(); pruneUpdates(); pruneInserts(); pruneFacts(); pruneSetup(); pruneFacts(); assertOriginalExceptionReproduced("Cannot reproduce the original problem after pruning the journal. " + "This is a bug!"); return journal; } private void dropOldestUpdates() { logger.info("Dropping oldest updates...", journal.getMoveOperations().size()); TestGenHeadCuttingMutator<TestGenKieSessionOperation> m = new TestGenHeadCuttingMutator<>(journal.getMoveOperations()); while (m.canMutate()) { long start = System.currentTimeMillis(); TestGenKieSessionJournal testJournal = new TestGenKieSessionJournal(journal.getFacts(), journal.getInitialInserts(), m.mutate()); boolean reproduced = reproduce(testJournal); double tookSeconds = (System.currentTimeMillis() - start) / 1000d; String outcome = reproduced ? "Reproduced" : "Can't reproduce"; logger.debug(" {} with journal size: {} (took {}s)", outcome, m.getResult().size(), tookSeconds); if (!reproduced) { m.revert(); } } journal = new TestGenKieSessionJournal(journal.getFacts(), journal.getInitialInserts(), m.getResult()); logger.info("{} updates remaining.", journal.getMoveOperations().size()); } private void pruneUpdates() { logger.info("Pruning updates...", journal.getMoveOperations().size()); TestGenRemoveRandomBlockMutator<TestGenKieSessionOperation> m = new TestGenRemoveRandomBlockMutator<>(journal.getMoveOperations()); while (m.canMutate()) { logger.debug(" Current journal size: {}", m.getResult().size()); TestGenKieSessionJournal testJournal = new TestGenKieSessionJournal(journal.getFacts(), journal.getInitialInserts(), m.mutate()); boolean reproduced = reproduce(testJournal); String outcome = reproduced ? "Reproduced" : "Can't reproduce"; List<TestGenKieSessionOperation> block = m.getRemovedBlock(); logger.debug(" {} without block of {} [{} - {}]", outcome, block.size(), block.get(0), block.get(block.size() - 1)); if (!reproduced) { m.revert(); } } journal = new TestGenKieSessionJournal(journal.getFacts(), journal.getInitialInserts(), m.getResult()); logger.info("{} updates remaining.", journal.getMoveOperations().size()); } private void pruneInserts() { logger.info("Pruning inserts...", journal.getInitialInserts().size()); TestGenRemoveRandomBlockMutator<TestGenKieSessionInsert> m = new TestGenRemoveRandomBlockMutator<>(journal.getInitialInserts()); while (m.canMutate()) { logger.debug(" Current journal size: {}", m.getResult().size()); TestGenKieSessionJournal testJournal = new TestGenKieSessionJournal(journal.getFacts(), m.mutate(), journal.getMoveOperations()); boolean reproduced = reproduce(testJournal); String outcome = reproduced ? "Reproduced" : "Can't reproduce"; List<TestGenKieSessionInsert> block = m.getRemovedBlock(); logger.debug(" {} without block of {} [{} - {}]", outcome, block.size(), block.get(0), block.get(block.size() - 1)); if (!reproduced) { m.revert(); } } journal = new TestGenKieSessionJournal(journal.getFacts(), m.getResult(), journal.getMoveOperations()); logger.info("{} inserts remaining.", journal.getInitialInserts().size()); } private void pruneFacts() { logger.info("Pruning {} facts...", journal.getFacts().size()); ArrayList<TestGenFact> minimal = new ArrayList<>(); for (TestGenKieSessionInsert insert : journal.getInitialInserts()) { addWithDependencies(insert.getFact(), minimal); } for (TestGenKieSessionOperation op : journal.getMoveOperations()) { if (op.getClass().equals(TestGenKieSessionUpdate.class)) { TestGenFact f = ((TestGenKieSessionUpdate) op).getValue(); addWithDependencies(f, minimal); } } journal.getFacts().retainAll(minimal); logger.info("{} facts remaining.", journal.getFacts().size()); } private static void addWithDependencies(TestGenFact f, List<TestGenFact> factList) { if (factList.contains(f)) { return; } factList.add(f); for (TestGenFact dependency : f.getDependencies()) { addWithDependencies(dependency, factList); } } private void pruneSetup() { logger.info("Pruning fact setup code...", journal.getFacts().size()); long disabled = journal.getFacts().stream() .flatMap(fact -> fact.getFields().stream()) // for all fields of all facts .filter(field -> { field.setActive(false); // when disabled if (reproduce(journal)) { // and the exception is still reproducible return true; // count it } else { // otherwise reset to active (the field must be set in order to reproduce the exception) field.setActive(true); return false; } }).count(); logger.info("Disabled {} field setters.", disabled); } private boolean reproduce(TestGenKieSessionJournal testJournal) { return reproducer.isReproducible(testJournal); } private void assertOriginalExceptionReproduced(String message) { reproducer.assertReproducible(journal, message); } }