Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion grails/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<target name="-download-ivy" unless="ivy.available">
<mkdir dir="${ivy.jar.dir}"/>
<!-- download Ivy from web site so that it can be used even without any special installation -->
<get src="http://www.apache.org/dist/ant/ivy/${ivy.install.version}/apache-ivy-${ivy.install.version}-bin.zip"
<get src="http://archive.apache.org/dist/ant/ivy/${ivy.install.version}/apache-ivy-${ivy.install.version}-bin.zip"
dest="${ivy.home}/ivy.zip" usetimestamp="true" verbose="true"/>
<unzip src="${ivy.home}/ivy.zip" dest="${ivy.jar.dir}">
<patternset>
Expand Down
2 changes: 1 addition & 1 deletion grails/grails-app/conf/Config.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ uiperformance.exclusions = [
]

// statSleep is the time to sleep between calculating test run stats
statSleep = 1000
statSleep = 3000

// testOutcomeAndTestRunInitSleep is the time to sleep between initializing TestOutcomes and TestRuns
testOutcomeAndTestRunInitSleepTime = 5000
Expand Down
16 changes: 16 additions & 0 deletions grails/grails-app/conf/DataSource.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='org.hibernate.cache.EhCacheProvider'
dialect='cuanto.CustomDialect'
//show_sql=true
}
// environment specific settings
environments {
Expand All @@ -21,13 +23,27 @@ environments {
}
}
test {
hibernate {
dialect='org.hibernate.dialect.HSQLDialect'
}
dataSource {
dbCreate = "update"
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:testDb"
username = "sa"
password = ""
}
// use this for tag filter tests which are dependent on MySQL
/*
dataSource {
dbCreate = "update"
pooled = true
username = "root"
password = ""
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/cuanto-test?autoreconnect=true"
}
*/
}
production {
dataSource {
Expand Down
6 changes: 4 additions & 2 deletions grails/grails-app/conf/spring/resources.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import cuanto.parsers.CuantoManualParser
import cuanto.parsers.TestNgParser
import cuanto.queryprocessor.TestRunQueryModule
import cuanto.queryprocessor.TestResultIsFailureQueryModule
import cuanto.queryprocessor.TestResultIsNonPassingQueryModule
import cuanto.queryprocessor.TestResultQueryModule
import cuanto.queryprocessor.TestCaseFullNameQueryModule
import cuanto.queryprocessor.TestCaseParametersQueryModule
Expand Down Expand Up @@ -75,6 +76,7 @@ beans = {
new TestRunQueryModule(),
new TestResultIsFailureQueryModule(),
new TestResultIsSkipQueryModule(),
new TestResultIsNonPassingQueryModule(),
new TestResultQueryModule(),
new TestCaseFullNameQueryModule(),
new TestCaseParametersQueryModule(),
Expand All @@ -88,11 +90,11 @@ beans = {
new TestCaseQueryModule(),
new NoteQueryModule(),
new TestOutputQueryModule(),
new TagNameQueryModule(),
new HasTagsQueryModule(),
new DateExecutedQueryModule(),
new FailureStatusChangedQueryModule(),
new TestOutcomeHasAllPropertiesQueryModule()
new TestOutcomeHasAllPropertiesQueryModule(),
new TagNameQueryModule()
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,10 @@ class ProjectController {
if (params.id) {
Project proj = Project.get(params.id)
if (proj) {
def urlBase = request.scheme + "://" + request.serverName + ":" + request.serverPort + "/" + grailsApplication.metadata.'app.name'
render(feedType: "rss", feedVersion: "2.0") {
title = "${proj.toString()} Recent Results"
link = "http://your.test.server/yourController/feed"
link = createLink(controller: "project", action: "feed", id: proj.id, base: urlBase)
description = "Recent Test Results for ${proj.toString()}"

List<TestRun> testRuns = testRunService.getTestRunsForProject([id: proj.id, offset: 0, max: Defaults.ItemsPerFeed,
Expand All @@ -214,6 +215,7 @@ class ProjectController {
def stats = TestRunStats.findByTestRun(testRun)
if (stats){
entry(testRun.dateExecuted) {
// TODO: summary does not appear to be a valid action. what's it supposed to link to?
link = createLink(controller: "testRun", action: "summary", id: testRun.id)
def feedTxt = testRunService.getFeedText(testRun, stats)
return feedTxt
Expand Down
14 changes: 12 additions & 2 deletions grails/grails-app/controllers/cuanto/TestRunController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ class TestRunController {
def outcomes = {
Map results = testOutcomeService.getTestOutcomeQueryResultsForParams(params)

def columns
if (params.columns) {
// I don't know why occasionally the columns come in as an array instead of String
if (params.columns.class.isArray() && !params.columns.isEmpty()) {
params.columns = params.columns[0]
}
columns = params.columns.tokenize(',').collect{ it.tokenize('|') }
}

withFormat {
json {
def formatter = testOutcomeService.getTestCaseFormatter(params.tcFormat)
Expand All @@ -175,11 +184,11 @@ class TestRunController {
}
csv {
response.contentType = "text/csv"
render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, ",")
render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, columns, ",")
}
tsv {
response.contentType = 'text/tab-separated-values'
render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, "\t")
render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, columns, "\t")
}
}
}
Expand Down Expand Up @@ -447,6 +456,7 @@ class TestRunController {
filterList += [id: "newpasses", value: "New Passes"]
filterList += [id: "allresults", value: "All Results"]
filterList += [id: "allquarantined", value: "All Quarantined"]
filterList += [id: "nonPassing", value: "All Non-Passing"]
return filterList
}

Expand Down
28 changes: 20 additions & 8 deletions grails/grails-app/domain/cuanto/TestOutcomeQueryFilter.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class TestOutcomeQueryFilter implements QueryFilter {
testRun(nullable: true)
isFailure(nullable: true)
isSkip(nullable: true)
isNonPassing(nullable: true)
testResult(nullable:true)
testCaseFullName(nullable: true)
testCaseParameters(nullable: true)
Expand All @@ -53,7 +54,7 @@ public class TestOutcomeQueryFilter implements QueryFilter {
TestOutcomeQueryFilter() {}

TestOutcomeQueryFilter(TestOutcomeQueryFilter filterToCopy) {
["testRun", "isFailure", "isSkip", "testResult", "testCaseFullName", "testCaseParameters", "testCasePackage", "project",
["testRun", "isFailure", "isSkip", "isNonPassing", "testResult", "testCaseFullName", "testCaseParameters", "testCasePackage", "project",
"testResultIncludedInCalculations", "isAnalyzed", "analysisState", "bug", "owner", "testCase", "note",
"testOutput", "dateCriteria", "sorts", "queryOffset", "queryMax", "isFailureStatusChanged",
"hasAllTestOutcomeProperties", "successRate"].each {
Expand Down Expand Up @@ -84,6 +85,11 @@ public class TestOutcomeQueryFilter implements QueryFilter {
*/
Boolean isSkip

/**
* If true then all returned outcomes that are considered failures or skips will be returned.
* If null, outcomes will not be returned based on isNonPassing status
*/
Boolean isNonPassing

/**
* If not null, then all returned outcomes must have this exact TestResult.
Expand Down Expand Up @@ -221,7 +227,11 @@ public class TestOutcomeQueryFilter implements QueryFilter {


String selectClause() {
def select = "select distinct t"
def select = "select distinct "
if (tags) {
select += "group_concat(tag_0.name), "
}
select += " t"
if (sorts) {
def newList = sorts.collect{"t." + it.sort}
select += ", " + newList.join(", ")
Expand All @@ -235,7 +245,9 @@ public class TestOutcomeQueryFilter implements QueryFilter {


String countClause() {
"select count(distinct t) "
// don't know how to combine group_concat with a count
//"select count(distinct t) "
return selectClause()
}


Expand Down Expand Up @@ -300,10 +312,10 @@ public class TestOutcomeQueryFilter implements QueryFilter {


public List resultTransform(List results) {
if (sorts) {
return results.collect{it[0]}
} else {
return results
}
if (sorts || tags) {
return results.collect{ it[tags ? 1 : 0] }
} else {
return results
}
}
}
4 changes: 1 addition & 3 deletions grails/grails-app/jobs/TestRunStatsJob.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ class TestRunStatsJob {
}

def execute() {
if (!statisticService.processingTestRunStats) {
statisticService.processTestRunStats()
}
statisticService.processTestRunStats()
}
}
2 changes: 1 addition & 1 deletion grails/grails-app/services/cuanto/DataService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ class DataService {
} else {
results = TestOutcome.executeQuery(cuantoQuery.hql)
}
return results[0]
return results.size()
}


Expand Down
2 changes: 1 addition & 1 deletion grails/grails-app/services/cuanto/ProjectService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,4 @@ class ProjectService {
deleteProjectGroupIfUnused(group)
}
}
}
}
101 changes: 61 additions & 40 deletions grails/grails-app/services/cuanto/StatisticService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ class StatisticService {
def testRunService
def grailsApplication

def outcomeBatchSize = 100
def queuedTestResultStats = [] as Set

int numRecentTestOutcomes = 40
Boolean processingTestRunStats = false;
final private static String queueLock = "Test Run Stat Queue Lock"
final private static String calcLock = "Test Run Calculation Lock"

Expand Down Expand Up @@ -103,32 +105,36 @@ class StatisticService {
def processTestRunStats() {
synchronized (calcLock) {
def queueSize = QueuedTestRunStat.list().size()
while (queueSize > 0) {
log.debug "${queueSize} items in stat queue"
while (queueSize > 0 || queuedTestResultStats.size() > 0) {
log.debug "${queueSize} items in stat queue and ${queuedTestResultStats.size()} in result stat queue"
QueuedTestRunStat queuedItem = getFirstTestRunIdInQueue()
if (queuedItem) {
log.info "Calculating stats for test run ${queuedItem.testRunId}"
def startTime = System.currentTimeMillis()
try {
queuedTestResultStats.add(queuedItem.testRunId)
QueuedTestRunStat.withTransaction {
calculateTestRunStats(queuedItem.testRunId)
calculateTestOutcomeStats(queuedItem.testRunId)
queuedItem.delete(flush: true)
queueSize = QueuedTestRunStat.list().size()
}
} catch (OptimisticLockingFailureException e) {
log.info "OptimisticLockingFailureException for test run ${queuedItem.testRunId}"
// leave it in queue so it gets tried again
} catch (HibernateOptimisticLockingFailureException e) {
log.info "HibernateOptimisticLockingFailureException for test run ${queuedItem.testRunId}"
} catch (StaleObjectStateException e) {
log.info "StaleObjectStateException for test run ${queuedItem.testRunId}"
// leave it in queue so it gets tried again
}
}
}
if (grailsApplication.config.statSleep) {
sleep(grailsApplication.config.statSleep)
def elapsed = System.currentTimeMillis() - startTime
log.info "Calculated stats for ${queuedItem.testRunId} in ${elapsed} ms"
} catch (Exception e) {
// re-queue to the "end" of the queue so other items aren't starved
log.info "Exception for test run ${queuedItem.testRunId} - ${e.toString()}"
queuedItem.dateCreated = new Date()
dataService.saveDomainObject(queuedItem, true)
}
} else {
// process some queued test outcome stats
QueuedTestRunStat.withTransaction {
calculateTestOutcomeStats(queuedTestResultStats.toList().first())
}
}
if (grailsApplication.config.statSleep) {
sleep(grailsApplication.config.statSleep)
}
queueSize = QueuedTestRunStat.list().size()
}

}
}

Expand All @@ -143,12 +149,17 @@ class StatisticService {


QueuedTestRunStat getFirstTestRunIdInQueue() {
def latest = QueuedTestRunStat.listOrderByDateCreated(max: 1)
if (latest.size() == 0) {
return null
} else {
return latest[0]
}
synchronized (queueLock) {
QueuedTestRunStat.withTransaction {
def queuedItem = null
def latest = QueuedTestRunStat.listOrderByDateCreated(max: 1)
if (latest.size() != 0) {
queuedItem = latest[0]
queuedItem.delete(flush: true)
}
return queuedItem
}
}
}


Expand Down Expand Up @@ -247,20 +258,30 @@ class StatisticService {
if (!testRun) {
log.error "Couldn't find test run ${testRunId}"
} else {
def counter = outcomeBatchSize
def startTime = System.currentTimeMillis()
List<TestOutcome> testOutcomes = TestOutcome.findAllByTestRun(testRun)
for (TestOutcome testOutcome : testOutcomes)
{
testOutcome.lock()
def stats = new TestOutcomeStats()
dataService.saveDomainObject(stats, true)
testOutcome.testOutcomeStats = stats
List<String> recentTestResults = TestOutcome.executeQuery(
"SELECT to.testResult FROM cuanto.TestOutcome to WHERE to.testCase = ?",
[testOutcome.testCase], [max: numRecentTestOutcomes, sort: 'id', order: 'desc'])
calculateStreak(testOutcome, recentTestResults)
calculateSuccessRate(testOutcome, recentTestResults)
testOutcome.save()
for (TestOutcome testOutcome : testOutcomes) {
if (!testOutcome.testOutcomeStats) {
testOutcome.lock()
def stats = new TestOutcomeStats()
dataService.saveDomainObject(stats, true)
testOutcome.testOutcomeStats = stats
List<String> recentTestResults = TestOutcome.executeQuery(
"SELECT to.testResult FROM cuanto.TestOutcome to WHERE to.testCase = ?",
[testOutcome.testCase], [max: numRecentTestOutcomes, sort: 'id', order: 'desc'])
calculateStreak(testOutcome, recentTestResults)
calculateSuccessRate(testOutcome, recentTestResults)
testOutcome.save()
if (counter-- <= 0) {
log.info "processed a batch of ${outcomeBatchSize} outcome stats for test run ${testRunId} in " + (System.currentTimeMillis() - startTime) + " ms"
return
}
}
}
// made it to the end of for every testOutcome!
log.info "finished processing test outcome stats for test run ${testRunId} in " + (System.currentTimeMillis() - startTime) + " ms"
queuedTestResultStats.remove(testRunId)
}
}

Expand Down Expand Up @@ -324,7 +345,7 @@ class StatisticService {


List getTagStatistics(TestRun testRun) {
testRun = testRun.refresh()
testRun = testRun.refresh()
def tagStats = []
if (Environment.current != Environment.TEST) {
// The HSQL database doesn't like the following query, so we won't do it in testing.
Expand All @@ -347,7 +368,7 @@ class StatisticService {
tagStat.skipped = skipped ?: 0;
log.debug "${skipped} skipped for ${tag.name}"

def duration = rawStats.collect { it[iDuration] }.sum()
def duration = rawStats.collect { it[iDuration] ?: 0 }.sum()
tagStat.duration = Math.max(0, duration ?: 0);

def total = rawStats.collect {it[iCount]}.sum()
Expand Down
Loading