| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- package Test::Mapper::DB;
- use strict;
- use warnings;
- use DBI;
- use File::Touch;
- use File::Slurper qw{read_text};
- # ABSTRACT: Persistent storage for what test maps where for what build.
- # We hold possibly multiple DBHes to support testing multiple products.
- my $dbh = {};
- sub dbh {
- my ( $dbname ) = @_;
- return $dbh->{$dbname} if $dbh->{$dbname};
- File::Touch::touch($dbname) unless -f $dbname;
- my $db = DBI->connect( "dbi:SQLite:dbname=$dbname", "", "" );
- # Turn on fkeys before schema creation so FK constraints take effect
- $db->do("PRAGMA foreign_keys = ON") or die "Could not enable foreign keys";
- # Turn on WAL mode for concurrent reads
- $db->do("PRAGMA journal_mode = WAL") or die "Could not enable WAL mode";
- my $schema = join( "", readline(DATA) );
- if ($schema) {
- $db->{sqlite_allow_multiple_statements} = 1;
- $db->do($schema) or die "Could not ensure database consistency: " . $db->errstr;
- $db->{sqlite_allow_multiple_statements} = 0;
- }
- $dbh->{$dbname} = $db;
- return $db;
- }
- 1;
- __DATA__
- -- Should be pretty self-explanatory, DRY
- CREATE TABLE IF NOT EXISTS product (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- product TEXT NOT NULL UNIQUE
- );
- CREATE TABLE IF NOT EXISTS environment (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- environment TEXT NOT NULL,
- product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
- UNIQUE(environment, product_id) ON CONFLICT IGNORE
- );
- CREATE TABLE IF NOT EXISTS build (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- build TEXT NOT NULL,
- product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
- UNIQUE(build, product_id) ON CONFLICT IGNORE
- );
- CREATE TABLE IF NOT EXISTS route (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- route TEXT NOT NULL,
- product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
- UNIQUE(route, product_id) ON CONFLICT IGNORE
- );
- CREATE TABLE IF NOT EXISTS test (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- filename TEXT NOT NULL,
- product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
- UNIQUE(filename, product_id) ON CONFLICT IGNORE
- );
- -- run_by is used to coordinate running, what hasn't been taken yet etc
- -- result is a result code that visualization programs can use for historical charts
- -- The idea here is that 'people just wanna see what failed', and this helps narrow that down
- -- start/finish is so you can figure historical data/charts
- CREATE TABLE IF NOT EXISTS test_for_build (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- build_id INTEGER NOT NULL REFERENCES build(id) ON DELETE CASCADE,
- test_id INTEGER NOT NULL REFERENCES test(id) ON DELETE CASCADE,
- run_by TEXT,
- result INTEGER,
- start INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
- finish INTEGER DEFAULT NULL,
- UNIQUE(build_id, test_id) ON CONFLICT ROLLBACK
- );
- -- Actually do the mapping of test -> what files changing trigger its running
- -- One tends to use differing resources on different environments.
- -- It is however assumed that the tests (or the cron inserting resources/tests) discriminates in this regard.
- CREATE TABLE IF NOT EXISTS resource (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- environment_id INTEGER NOT NULL REFERENCES environment(id) ON DELETE CASCADE,
- filename TEXT NOT NULL,
- UNIQUE(environment_id, filename) ON CONFLICT IGNORE
- );
- -- Indexed by resource_id, as that's how a build is done "I have x changed resources, wat run?"
- CREATE TABLE IF NOT EXISTS tested_by (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- resource_id INTEGER NOT NULL REFERENCES resource(id) ON DELETE CASCADE,
- test_id INTEGER NOT NULL REFERENCES test(id) ON DELETE CASCADE,
- UNIQUE(resource_id, test_id) ON CONFLICT IGNORE
- );
- -- Lifecycle:
- -- Have list of files from `git log`
- -- Run analysis script that returns hash test_file => [source_file, ...]
- -- INSERT OR IGNORE INTO tested_by (resource_id, test_id) VALUES (...);
- CREATE VIEW IF NOT EXISTS to_run AS
- SELECT
- t.filename AS test_file,
- t.id AS test_id,
- r.filename AS source_file,
- e.environment AS environment
- FROM resource AS r
- JOIN tested_by AS tb ON tb.resource_id = r.id
- JOIN test AS t ON t.id = tb.test_id
- JOIN environment AS e ON e.id = r.environment_id
- ;
- -- Lifecycle:
- -- Cron dumps rows into resource/tested_by, when time comes to run we grab the list of stuff:
- -- SELECT test_file FROM to_run WHERE source_file IN (...) GROUP BY test_file;
- -- We make a run
- -- INSERT OR IGNORE INTO test_for_build (build_id, test_id) SELECT b.id, tr.test_id
- -- FROM build AS b, to_run AS tr WHERE b.build='someSha' AND tr.source_file IN (...);
- -- We run a test
- -- UPDATE test_for_build SET run_by='processid:90210' WHERE build_id=? AND test_id=?;
- -- It completes. The end.
- -- UPDATE test_for_build SET result=1, finish=CURRENT_TIMESTAMP WHERE build_id=? AND test_id=?;
- CREATE VIEW IF NOT EXISTS run AS
- SELECT
- p.product,
- e.environment,
- b.build,
- r.route,
- t.filename,
- tb.run_by,
- tb.result
- FROM product AS p
- JOIN build AS b ON p.id = b.product_id
- JOIN test_for_build AS tb ON b.id = tb.build_id
- JOIN environment AS e ON p.id = e.product_id
- JOIN route AS r ON p.id = r.product_id
- JOIN test AS t ON tb.test_id = t.id
- ;
|