1
0

DB.pm 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package Test::Mapper::DB;
  2. use strict;
  3. use warnings;
  4. use DBI;
  5. use File::Touch;
  6. # ABSTRACT: Persistent storage for what test maps where for what build.
  7. # We hold possibly multiple DBHes to support testing multiple products.
  8. my $dbh = {};
  9. sub dbh {
  10. my ( $dbname ) = @_;
  11. return $dbh->{$dbname} if $dbh->{$dbname};
  12. File::Touch::touch($dbname) unless -f $dbname;
  13. my $db = DBI->connect( "dbi:SQLite:dbname=$dbname", "", "" );
  14. # Turn on fkeys before schema creation so FK constraints take effect
  15. $db->do("PRAGMA foreign_keys = ON") or die "Could not enable foreign keys";
  16. # Turn on WAL mode for concurrent reads
  17. $db->do("PRAGMA journal_mode = WAL") or die "Could not enable WAL mode";
  18. my $schema = join( "", readline(DATA) );
  19. if ($schema) {
  20. $db->{sqlite_allow_multiple_statements} = 1;
  21. $db->do($schema) or die "Could not ensure database consistency: " . $db->errstr;
  22. $db->{sqlite_allow_multiple_statements} = 0;
  23. }
  24. $dbh->{$dbname} = $db;
  25. return $db;
  26. }
  27. 1;
  28. __DATA__
  29. -- Should be pretty self-explanatory, DRY
  30. CREATE TABLE IF NOT EXISTS product (
  31. id INTEGER PRIMARY KEY AUTOINCREMENT,
  32. product TEXT NOT NULL UNIQUE
  33. );
  34. CREATE TABLE IF NOT EXISTS environment (
  35. id INTEGER PRIMARY KEY AUTOINCREMENT,
  36. environment TEXT NOT NULL,
  37. product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
  38. UNIQUE(environment, product_id) ON CONFLICT IGNORE
  39. );
  40. CREATE TABLE IF NOT EXISTS build (
  41. id INTEGER PRIMARY KEY AUTOINCREMENT,
  42. build TEXT NOT NULL,
  43. product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
  44. UNIQUE(build, product_id) ON CONFLICT IGNORE
  45. );
  46. CREATE TABLE IF NOT EXISTS route (
  47. id INTEGER PRIMARY KEY AUTOINCREMENT,
  48. route TEXT NOT NULL,
  49. product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
  50. UNIQUE(route, product_id) ON CONFLICT IGNORE
  51. );
  52. CREATE TABLE IF NOT EXISTS test (
  53. id INTEGER PRIMARY KEY AUTOINCREMENT,
  54. filename TEXT NOT NULL,
  55. product_id INTEGER NOT NULL REFERENCES product(id) ON DELETE CASCADE,
  56. UNIQUE(filename, product_id) ON CONFLICT IGNORE
  57. );
  58. -- run_by is used to coordinate running, what hasn't been taken yet etc
  59. -- result is a result code that visualization programs can use for historical charts
  60. -- The idea here is that 'people just wanna see what failed', and this helps narrow that down
  61. -- start/finish is so you can figure historical data/charts
  62. CREATE TABLE IF NOT EXISTS test_for_build (
  63. id INTEGER PRIMARY KEY AUTOINCREMENT,
  64. build_id INTEGER NOT NULL REFERENCES build(id) ON DELETE CASCADE,
  65. test_id INTEGER NOT NULL REFERENCES test(id) ON DELETE CASCADE,
  66. run_by TEXT,
  67. result INTEGER,
  68. start INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
  69. finish INTEGER DEFAULT NULL,
  70. UNIQUE(build_id, test_id) ON CONFLICT ROLLBACK
  71. );
  72. -- Actually do the mapping of test -> what files changing trigger its running
  73. -- One tends to use differing resources on different environments.
  74. -- It is however assumed that the tests (or the cron inserting resources/tests) discriminates in this regard.
  75. CREATE TABLE IF NOT EXISTS resource (
  76. id INTEGER PRIMARY KEY AUTOINCREMENT,
  77. environment_id INTEGER NOT NULL REFERENCES environment(id) ON DELETE CASCADE,
  78. filename TEXT NOT NULL,
  79. UNIQUE(environment_id, filename) ON CONFLICT IGNORE
  80. );
  81. -- Indexed by resource_id, as that's how a build is done "I have x changed resources, wat run?"
  82. CREATE TABLE IF NOT EXISTS tested_by (
  83. id INTEGER PRIMARY KEY AUTOINCREMENT,
  84. resource_id INTEGER NOT NULL REFERENCES resource(id) ON DELETE CASCADE,
  85. test_id INTEGER NOT NULL REFERENCES test(id) ON DELETE CASCADE,
  86. UNIQUE(resource_id, test_id) ON CONFLICT IGNORE
  87. );
  88. -- Lifecycle:
  89. -- Have list of files from `git log`
  90. -- Run analysis script that returns hash test_file => [source_file, ...]
  91. -- INSERT OR IGNORE INTO tested_by (resource_id, test_id) VALUES (...);
  92. CREATE VIEW IF NOT EXISTS to_run AS
  93. SELECT
  94. t.filename AS test_file,
  95. t.id AS test_id,
  96. r.filename AS source_file,
  97. e.environment AS environment
  98. FROM resource AS r
  99. JOIN tested_by AS tb ON tb.resource_id = r.id
  100. JOIN test AS t ON t.id = tb.test_id
  101. JOIN environment AS e ON e.id = r.environment_id
  102. ;
  103. -- Build run summary: what tests were queued/run/completed for each build.
  104. -- Use: SELECT * FROM run WHERE build='abc123' AND result IS NULL (unclaimed);
  105. -- SELECT * FROM run WHERE build='abc123' AND result IS NOT NULL (finished);
  106. CREATE VIEW IF NOT EXISTS run AS
  107. SELECT
  108. p.product,
  109. b.build,
  110. t.filename AS test_file,
  111. tfb.run_by,
  112. tfb.result,
  113. tfb.start,
  114. tfb.finish
  115. FROM test_for_build AS tfb
  116. JOIN build AS b ON b.id = tfb.build_id
  117. JOIN test AS t ON t.id = tfb.test_id
  118. JOIN product AS p ON p.id = b.product_id
  119. ;