Permalink
Browse files

working on handling database constraints (primary keys, unique column…

…s, column and table constraints)
  • Loading branch information...
jgaleotti committed Oct 1, 2018
1 parent cf323c5 commit c6deb322698a17a5e8b339837343d0a88ca0e090
Showing with 989 additions and 138 deletions.
  1. +22 −0 ...controller/src/test/java/org/evomaster/clientJava/controller/internal/db/SchemaExtractorTest.java
  2. +131 −8 core/src/main/kotlin/org/evomaster/core/database/DbAction.kt
  3. +1 −1 core/src/main/kotlin/org/evomaster/core/output/TestCaseWriter.kt
  4. +28 −3 core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt
  5. +14 −0 core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestFitness.kt
  6. +1 −3 core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestSampler.kt
  7. +8 −4 core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestStructureMutator.kt
  8. +22 −7 core/src/main/kotlin/org/evomaster/core/search/Individual.kt
  9. +12 −2 core/src/main/kotlin/org/evomaster/core/search/gene/ArrayGene.kt
  10. +11 −3 core/src/main/kotlin/org/evomaster/core/search/gene/Base64StringGene.kt
  11. +13 −5 core/src/main/kotlin/org/evomaster/core/search/gene/BooleanGene.kt
  12. +14 −4 core/src/main/kotlin/org/evomaster/core/search/gene/DateGene.kt
  13. +13 −5 core/src/main/kotlin/org/evomaster/core/search/gene/DateTimeGene.kt
  14. +20 −7 core/src/main/kotlin/org/evomaster/core/search/gene/DisruptiveGene.kt
  15. +10 −2 core/src/main/kotlin/org/evomaster/core/search/gene/DoubleGene.kt
  16. +8 −0 core/src/main/kotlin/org/evomaster/core/search/gene/EnumGene.kt
  17. +11 −2 core/src/main/kotlin/org/evomaster/core/search/gene/FloatGene.kt
  18. +6 −0 core/src/main/kotlin/org/evomaster/core/search/gene/Gene.kt
  19. +13 −6 core/src/main/kotlin/org/evomaster/core/search/gene/IntegerGene.kt
  20. +8 −0 core/src/main/kotlin/org/evomaster/core/search/gene/LongGene.kt
  21. +12 −1 core/src/main/kotlin/org/evomaster/core/search/gene/MapGene.kt
  22. +10 −0 core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt
  23. +16 −8 core/src/main/kotlin/org/evomaster/core/search/gene/OptionalGene.kt
  24. +19 −1 core/src/main/kotlin/org/evomaster/core/search/gene/SqlAutoIncrementGene.kt
  25. +10 −2 core/src/main/kotlin/org/evomaster/core/search/gene/SqlForeignKeyGene.kt
  26. +18 −12 core/src/main/kotlin/org/evomaster/core/search/gene/SqlPrimaryKeyGene.kt
  27. +11 −2 core/src/main/kotlin/org/evomaster/core/search/gene/SqlTimestampGene.kt
  28. +14 −6 core/src/main/kotlin/org/evomaster/core/search/gene/StringGene.kt
  29. +18 −9 core/src/main/kotlin/org/evomaster/core/search/gene/TimeGene.kt
  30. +28 −21 core/src/main/kotlin/org/evomaster/core/search/mutator/StandardMutator.kt
  31. +8 −1 core/src/main/kotlin/org/evomaster/core/search/service/Mutator.kt
  32. +411 −0 core/src/test/kotlin/org/evomaster/core/database/DbActionRepairTest.kt
  33. +2 −0 core/src/test/kotlin/org/evomaster/core/database/ProxyPrintSqlExtractTest.kt
  34. +6 −12 core/src/test/kotlin/org/evomaster/core/output/TestCaseWriterTest.kt
  35. +10 −1 core/src/test/kotlin/org/evomaster/core/search/algorithms/constant/ConstantIndividual.kt
  36. +10 −0 core/src/test/kotlin/org/evomaster/core/search/algorithms/onemax/OneMaxIndividual.kt
  37. +10 −0 experiments/src/main/kotlin/org/evomaster/experiments/linear/LinearIndividual.kt
  38. +10 −0 experiments/src/main/kotlin/org/evomaster/experiments/pair/PairIndividual.kt
@@ -205,4 +205,26 @@ public void testTableConstraint() throws Exception {
}
@Test
public void testPrimaryKey() throws Exception {
String sqlCommand = "CREATE TABLE FOO (id INT, "
+ "primary key (id));";
SqlScriptRunner.execCommand(getConnection(), sqlCommand);
DbSchemaDto schema = SchemaExtractor.extract(getConnection());
assertEquals(1, schema.tables.size());
TableDto fooTable = schema.tables.stream().filter(t -> t.name.equalsIgnoreCase("Foo")).findAny().get();
assertEquals(1, fooTable.columns.size());
assertTrue(fooTable.columns.stream().anyMatch(c -> c.name.equalsIgnoreCase("id")));
assertEquals(true, fooTable.columns.get(0).primaryKey);
assertEquals(false, fooTable.columns.get(0).unique);
}
}
@@ -78,18 +78,141 @@ class DbAction(
}
}
if(javaClass.desiredAssertionStatus()) {
if (javaClass.desiredAssertionStatus()) {
//TODO refactor if/when Kotlin will support lazy asserts
assert(DbAction.verifyForeignKeys(actions))
}
/**
* Some genes (e.g. SQL timestamps) could still be invalid at this point
* due to randomization.
* It is needed to repair them before submitting the list.
*/
val genes = actions.flatMap { it.seeGenes() }
genes.forEach { GeneUtils.repairGenes(it.flatView()) }
}
private val DEFAULT_MAX_NUMBER_OF_ATTEMPTS_TO_REPAIR_ACTIONS = 5
/**
* Some actions might break schema constraints
* (such as unique columns or primary keys).
* This method tries to fix each unique column that is broken
*
* The repair algorithm first tries to modify genes.
* If it is unable to do, it starts removing Db Actions
*
* Returns true if the action list was fixed without truncating it.
* Returns false if the list needed to be truncated
*/
fun repairBrokenDbActionsList(actions: MutableList<DbAction>,
randomness: Randomness,
maxNumberOfAttemptsToRepairAnAction: Int = DEFAULT_MAX_NUMBER_OF_ATTEMPTS_TO_REPAIR_ACTIONS
): Boolean {
if (maxNumberOfAttemptsToRepairAnAction < 0) {
throw IllegalArgumentException("Maximum umber of attempts to fix an action should be non negative but it is: $maxNumberOfAttemptsToRepairAnAction")
}
var attemptCounter = 0
var previousActionIndexToRepair = -1
var geneToRepairAndActionIndex = findFirstOffendingGeneWithIndex(actions)
var geneToRepair = geneToRepairAndActionIndex.first
var actionIndexToRepair = geneToRepairAndActionIndex.second
while (geneToRepair != null && attemptCounter < maxNumberOfAttemptsToRepairAnAction) {
val previousGenes = actions.subList(0, geneToRepairAndActionIndex.second).flatMap { it.seeGenes() }
randomizeGene(geneToRepair, randomness, previousGenes)
if (actionIndexToRepair == previousActionIndexToRepair) {
//
attemptCounter++
} else if (actionIndexToRepair > previousActionIndexToRepair) {
attemptCounter = 0
previousActionIndexToRepair = actionIndexToRepair
} else {
throw IllegalStateException("Invalid last action repaired at position $previousActionIndexToRepair " +
" but new action to repair at position $actionIndexToRepair")
}
geneToRepairAndActionIndex = findFirstOffendingGeneWithIndex(actions)
geneToRepair = geneToRepairAndActionIndex.first
actionIndexToRepair = geneToRepairAndActionIndex.second
}
if (geneToRepair == null) {
return true
} else {
assert(actionIndexToRepair >= 0 && actionIndexToRepair < actions.size)
// truncate list of actions to make them valid
val truncatedListOfActions = actions.subList(0, actionIndexToRepair).toMutableList()
actions.clear()
actions.addAll(truncatedListOfActions)
return false
}
}
private fun randomizeGene(gene: Gene, randomness: Randomness, previousGenes: List<Gene>) {
when (gene) {
is SqlForeignKeyGene -> gene.randomize(randomness, true, previousGenes)
else ->
if (gene is SqlPrimaryKeyGene && gene.gene is SqlForeignKeyGene) {
//FIXME: this needs refactoring
gene.gene.randomize(randomness, true, previousGenes)
} else {
//TODO other cases
gene.randomize(randomness, true)
}
}
}
/**
* Returns true iff all action are valid wrt the schema.
* For example
*/
fun verifyActions(actions: List<DbAction>): Boolean {
return verifyUniqueColumns(actions)
&& verifyForeignKeys(actions)
}
/**
* Returns true if a insertion tries to insert a repeated value
* in a unique column
*/
fun verifyUniqueColumns(actions: List<DbAction>): Boolean {
val offendingGene = findFirstOffendingGeneWithIndex(actions)
return (offendingGene.first == null)
}
/**
* Returns the first offending gene found with the action index to the
* passed list where the gene was found.
* If no such gene is found, the function returns the tuple (-1,null).
*/
private fun findFirstOffendingGeneWithIndex(actions: List<Action>): Pair<Gene?, Int> {
val uniqueColumnValues = mutableMapOf<Pair<String, String>, MutableSet<Gene>>()
for ((actionIndex, action) in actions.withIndex()) {
if (action !is DbAction) {
continue
}
val tableName = action.table.name
action.seeGenes().forEach { g ->
val columnName = g.name
if (action.table.columns.filter { c ->
c.name == columnName && !c.autoIncrement && (c.unique || c.primaryKey)
}.isNotEmpty()) {
val key = Pair(tableName, columnName)
val genes = uniqueColumnValues.getOrPut(key) { mutableSetOf() }
if (genes.filter { otherGene -> otherGene.containsSameValueAs(g) }.isNotEmpty()) {
return Pair(g, actionIndex)
} else {
genes.add(g)
}
}
}
}
return Pair(null, -1)
}
}
@@ -80,7 +80,7 @@ class TestCaseWriter {
return lines
}
private fun handleDbInitialization(format: OutputFormat, dbInitialization: MutableList<DbAction>, lines: Lines) {
fun handleDbInitialization(format: OutputFormat, dbInitialization: MutableList<DbAction>, lines: Lines) {
dbInitialization.forEachIndexed { index, dbAction ->
@@ -4,6 +4,8 @@ import org.evomaster.core.database.DbAction
import org.evomaster.core.search.Action
import org.evomaster.core.search.Individual
import org.evomaster.core.search.gene.Gene
import org.evomaster.core.search.gene.GeneUtils
import org.evomaster.core.search.service.Randomness
class RestIndividual(val actions: MutableList<RestAction>,
@@ -27,8 +29,8 @@ class RestIndividual(val actions: MutableList<RestAction>,
override fun seeGenes(filter: GeneFilter): List<out Gene> {
return when(filter){
GeneFilter.ALL -> dbInitialization.flatMap(DbAction::seeGenes)
return when (filter) {
GeneFilter.ALL -> dbInitialization.flatMap(DbAction::seeGenes)
.plus(actions.flatMap(RestAction::seeGenes))
GeneFilter.NO_SQL -> actions.flatMap(RestAction::seeGenes)
@@ -48,7 +50,30 @@ class RestIndividual(val actions: MutableList<RestAction>,
return actions
}
override fun seeInitializingActions() : List<Action>{
override fun seeInitializingActions(): List<Action> {
return dbInitialization
}
override fun verifyInitializationActions(): Boolean {
return DbAction.verifyActions(dbInitialization.filterIsInstance<DbAction>())
}
override fun repairInitializationActions(randomness: Randomness) {
/**
* First repair SQL Genes (i.e. SQL Timestamps)
*/
GeneUtils.repairGenes(this.seeGenes(Individual.GeneFilter.ONLY_SQL).flatMap { it.flatView() })
/**
* Now repair databse constraints (primary keys, foreign keys, unique fields, etc.)
*/
if (!verifyInitializationActions()) {
DbAction.repairBrokenDbActionsList(dbInitialization, randomness)
assert(verifyInitializationActions())
}
}
}
@@ -5,8 +5,12 @@ import org.evomaster.clientJava.controllerApi.EMTestUtils
import org.evomaster.clientJava.controllerApi.dto.ExtraHeuristicDto
import org.evomaster.clientJava.controllerApi.dto.SutInfoDto
import org.evomaster.clientJava.controllerApi.dto.database.execution.ReadDbDataDto
import org.evomaster.core.database.DbAction
import org.evomaster.core.database.DbActionTransformer
import org.evomaster.core.database.EmptySelects
import org.evomaster.core.output.Lines
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.output.TestCaseWriter
import org.evomaster.core.problem.rest.*
import org.evomaster.core.problem.rest.auth.NoAuth
import org.evomaster.core.problem.rest.param.BodyParam
@@ -178,11 +182,21 @@ class RestFitness : FitnessFunction<RestIndividual>() {
return
}
val dto = DbActionTransformer.transform(ind.dbInitialization)
val ok = rc.executeDatabaseCommand(dto)
if (!ok) {
log.warn("Failed in executing database command")
val verifyOK = DbAction.verifyActions(ind.dbInitialization)
if (!verifyOK) {
log.warn("Broken Db Action")
}
val testCaseWriter = TestCaseWriter()
val lines = Lines()
testCaseWriter.handleDbInitialization(OutputFormat.JAVA_JUNIT_4, ind.dbInitialization, lines)
log.warn(lines.toString())
}
}
@@ -15,9 +15,6 @@ import org.evomaster.core.problem.rest.param.PathParam
import org.evomaster.core.remote.SutProblemException
import org.evomaster.core.remote.service.RemoteController
import org.evomaster.core.search.Action
import org.evomaster.core.search.gene.GeneUtils
import org.evomaster.core.search.gene.SqlForeignKeyGene
import org.evomaster.core.search.gene.SqlPrimaryKeyGene
import org.evomaster.core.search.service.Sampler
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@@ -166,6 +163,7 @@ class RestSampler : Sampler<RestIndividual>() {
?: throw IllegalStateException("No DB schema is available")
DbAction.randomizeDbActionGenes(actions, randomness)
return actions
}
@@ -1,6 +1,7 @@
package org.evomaster.core.problem.rest.service
import com.google.inject.Inject
import org.evomaster.core.database.DbAction
import org.evomaster.core.database.EmptySelects
import org.evomaster.core.problem.rest.HttpVerb
import org.evomaster.core.problem.rest.RestCallAction
@@ -18,7 +19,7 @@ class RestStructureMutator : StructureMutator() {
override fun addInitializingActions(individual: EvaluatedIndividual<*>) {
if(! config.shouldGenerateSqlData()){
if (!config.shouldGenerateSqlData()) {
return
}
@@ -36,7 +37,7 @@ class RestStructureMutator : StructureMutator() {
var missing = findMissing(es, ind)
while(! missing.isEmpty()){
while (!missing.isEmpty()) {
val first = missing.entries.first()
@@ -57,9 +58,12 @@ class RestStructureMutator : StructureMutator() {
missing = findMissing(es, ind)
}
if(config.generateSqlDataWithDSE){
if (config.generateSqlDataWithDSE) {
//TODO DSE could be plugged in here
}
ind.repairInitializationActions(randomness)
}
private fun findMissing(es: EmptySelects, ind: RestIndividual): Map<String, Set<String>> {
@@ -103,7 +107,7 @@ class RestStructureMutator : StructureMutator() {
SampleType.SMART -> throw IllegalStateException(
"SMART sampled individuals shouldn't be marked for structure mutations")
//this would be a bug
//this would be a bug
else -> throw IllegalStateException("Cannot handle sample type ${individual.sampleType}")
}
}
Oops, something went wrong.

0 comments on commit c6deb32

Please sign in to comment.