Skip to content

Commit ff44866

Browse files
authored
Cleanup test results when run/job is deleted (#111)
1 parent b04cf27 commit ff44866

File tree

6 files changed

+282
-23
lines changed

6 files changed

+282
-23
lines changed

src/main/java/io/jenkins/plugins/junit/storage/database/DatabaseSchemaLoader.java

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,54 @@
22

33
import hudson.init.Initializer;
44
import io.jenkins.plugins.junit.storage.JunitTestResultStorageConfiguration;
5-
import java.sql.SQLException;
6-
import javax.sql.DataSource;
75
import org.flywaydb.core.Flyway;
86
import org.jenkinsci.plugins.database.Database;
97
import org.jenkinsci.plugins.database.GlobalDatabaseConfiguration;
108
import org.kohsuke.accmod.Restricted;
119
import org.kohsuke.accmod.restrictions.NoExternalUse;
1210

11+
import javax.sql.DataSource;
12+
import java.util.logging.Level;
13+
import java.util.logging.Logger;
14+
1315
import static hudson.init.InitMilestone.SYSTEM_CONFIG_ADAPTED;
1416

1517
@Restricted(NoExternalUse.class)
1618
public class DatabaseSchemaLoader {
17-
19+
20+
private static final Logger LOGGER = Logger.getLogger(DatabaseSchemaLoader.class.getName());
21+
1822
static boolean MIGRATED;
1923

2024
@Initializer(after = SYSTEM_CONFIG_ADAPTED)
21-
public static void migrateSchema() throws SQLException {
25+
public static void migrateSchema() {
2226
JunitTestResultStorageConfiguration configuration = JunitTestResultStorageConfiguration.get();
2327
if (configuration.getStorage() instanceof DatabaseTestResultStorage) {
24-
DatabaseTestResultStorage storage = (DatabaseTestResultStorage) configuration.getStorage();
25-
DataSource dataSource = storage.getConnectionSupplier().database().getDataSource();
28+
try {
29+
DatabaseTestResultStorage storage = (DatabaseTestResultStorage) configuration.getStorage();
30+
DataSource dataSource = storage.getConnectionSupplier().database().getDataSource();
2631

27-
Database database = GlobalDatabaseConfiguration.get().getDatabase();
32+
Database database = GlobalDatabaseConfiguration.get().getDatabase();
2833

29-
assert database != null;
30-
String databaseDriverName = database.getClass().getName();
31-
String schemaLocation = "postgres";
32-
if (databaseDriverName.contains("mysql")) {
33-
schemaLocation = "mysql";
34+
assert database != null;
35+
String databaseDriverName = database.getClass().getName();
36+
String schemaLocation = "postgres";
37+
if (databaseDriverName.contains("mysql")) {
38+
schemaLocation = "mysql";
39+
}
40+
Flyway flyway = Flyway
41+
.configure(DatabaseSchemaLoader.class.getClassLoader())
42+
.baselineOnMigrate(true)
43+
.table("junit_flyway_schema_history")
44+
.dataSource(dataSource)
45+
.locations("db/migration/" + schemaLocation)
46+
.load();
47+
flyway.migrate();
48+
MIGRATED = true;
49+
} catch (Exception e) {
50+
// TODO add admin monitor
51+
LOGGER.log(Level.SEVERE, "Error migrating database, correct this error before using the junit plugin", e);
3452
}
35-
Flyway flyway = Flyway
36-
.configure(DatabaseSchemaLoader.class.getClassLoader())
37-
.baselineOnMigrate(true)
38-
.table("junit_flyway_schema_history")
39-
.dataSource(dataSource)
40-
.locations("db/migration/" + schemaLocation)
41-
.load();
42-
flyway.migrate();
43-
MIGRATED = true;
4453
}
4554
}
4655
}

src/main/java/io/jenkins/plugins/junit/storage/database/DatabaseTestResultStorage.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import edu.umd.cs.findbugs.annotations.CheckForNull;
44
import edu.umd.cs.findbugs.annotations.NonNull;
55
import hudson.Extension;
6+
import hudson.ExtensionList;
67
import hudson.Util;
78
import hudson.model.Job;
89
import hudson.model.Run;
@@ -31,19 +32,24 @@
3132
import java.util.Map;
3233
import java.util.Objects;
3334
import jenkins.model.Jenkins;
35+
import net.sf.json.JSONObject;
3436
import org.apache.commons.lang3.StringUtils;
3537
import org.jenkinsci.Symbol;
3638
import org.jenkinsci.plugins.database.Database;
3739
import org.jenkinsci.plugins.database.GlobalDatabaseConfiguration;
3840
import org.jenkinsci.remoting.SerializableOnlyOverRemoting;
3941
import org.kohsuke.stapler.DataBoundConstructor;
42+
import org.kohsuke.stapler.DataBoundSetter;
43+
import org.kohsuke.stapler.StaplerRequest;
4044

4145

4246
@Extension
4347
public class DatabaseTestResultStorage extends JunitTestResultStorage {
4448

4549
private transient ConnectionSupplier connectionSupplier;
4650

51+
private boolean skipCleanupRunsOnDeletion;
52+
4753
@DataBoundConstructor
4854
public DatabaseTestResultStorage() {}
4955

@@ -54,6 +60,15 @@ public ConnectionSupplier getConnectionSupplier() {
5460
return connectionSupplier;
5561
}
5662

63+
public boolean isSkipCleanupRunsOnDeletion() {
64+
return skipCleanupRunsOnDeletion;
65+
}
66+
67+
@DataBoundSetter
68+
public void setSkipCleanupRunsOnDeletion(boolean skipCleanupRunsOnDeletion) {
69+
this.skipCleanupRunsOnDeletion = skipCleanupRunsOnDeletion;
70+
}
71+
5772
@Override public RemotePublisher createRemotePublisher(Run<?, ?> build) throws IOException {
5873
try {
5974
getConnectionSupplier().connection(); // make sure we start a local server and create table first
@@ -269,6 +284,34 @@ private List<CaseResult> retrieveCaseResult(String whereCondition) {
269284
});
270285
}
271286

287+
288+
public Void deleteRun() {
289+
return query(connection -> {
290+
try (PreparedStatement statement = connection.prepareStatement(
291+
"DELETE FROM caseResults WHERE job = ? AND build = ?"
292+
)) {
293+
statement.setString(1, job);
294+
statement.setInt(2, build);
295+
296+
statement.execute();
297+
}
298+
return null;
299+
});
300+
}
301+
302+
public Void deleteJob() {
303+
return query(connection -> {
304+
try (PreparedStatement statement = connection.prepareStatement(
305+
"DELETE FROM caseResults WHERE job = ?"
306+
)) {
307+
statement.setString(1, job);
308+
309+
statement.execute();
310+
}
311+
return null;
312+
});
313+
}
314+
272315
@Override
273316
public List<PackageResult> getAllPackageResults() {
274317
return query(connection -> {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.jenkins.plugins.junit.storage.database;
2+
3+
import hudson.Extension;
4+
import hudson.model.Item;
5+
import hudson.model.Run;
6+
import hudson.model.listeners.ItemListener;
7+
import hudson.model.listeners.RunListener;
8+
import io.jenkins.plugins.junit.storage.FileJunitTestResultStorage;
9+
import io.jenkins.plugins.junit.storage.JunitTestResultStorage;
10+
import io.jenkins.plugins.junit.storage.TestResultImpl;
11+
12+
public class TestResultCleanupListener {
13+
14+
@Extension
15+
public static class RunCleanupListener extends RunListener<Run> {
16+
@Override
17+
public void onDeleted(Run run) {
18+
JunitTestResultStorage junitTestResultStorage = JunitTestResultStorage.find();
19+
if (junitTestResultStorage instanceof FileJunitTestResultStorage) {
20+
return;
21+
}
22+
23+
if (junitTestResultStorage instanceof DatabaseTestResultStorage) {
24+
DatabaseTestResultStorage storage = (DatabaseTestResultStorage) junitTestResultStorage;
25+
if (storage.isSkipCleanupRunsOnDeletion()) {
26+
return;
27+
}
28+
}
29+
30+
TestResultImpl testResult = junitTestResultStorage.load(run.getParent().getFullName(), run.getNumber());
31+
32+
if (testResult instanceof DatabaseTestResultStorage.TestResultStorage) {
33+
DatabaseTestResultStorage.TestResultStorage storage = (DatabaseTestResultStorage.TestResultStorage) testResult;
34+
35+
storage.deleteRun();
36+
}
37+
}
38+
}
39+
40+
@Extension
41+
public static class JobCleanupListener extends ItemListener {
42+
@Override
43+
public void onDeleted(Item item) {
44+
JunitTestResultStorage junitTestResultStorage = JunitTestResultStorage.find();
45+
if (junitTestResultStorage instanceof FileJunitTestResultStorage) {
46+
return;
47+
}
48+
49+
if (junitTestResultStorage instanceof DatabaseTestResultStorage) {
50+
DatabaseTestResultStorage storage = (DatabaseTestResultStorage) junitTestResultStorage;
51+
if (storage.isSkipCleanupRunsOnDeletion()) {
52+
return;
53+
}
54+
}
55+
56+
TestResultImpl testResult = junitTestResultStorage.load(item.getFullName(), 0);
57+
58+
if (testResult instanceof DatabaseTestResultStorage.TestResultStorage) {
59+
DatabaseTestResultStorage.TestResultStorage storage = (DatabaseTestResultStorage.TestResultStorage) testResult;
60+
storage.deleteJob();
61+
}
62+
}
63+
}
64+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
3+
<f:entry field="skipCleanupRunsOnDeletion">
4+
<f:checkbox title="${%Skip cleanup of test result on build deletion}"/>
5+
</f:entry>
6+
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<p>If checked tests results will not be automatically removed when a job or build is deleted.</p>
2+
3+
<p>If you need more complex test result cleanup then please raise an issue on GitHub.</p>

0 commit comments

Comments
 (0)