Index: lnt/trunk/lnt/server/reporting/dailyreport.py =================================================================== --- lnt/trunk/lnt/server/reporting/dailyreport.py +++ lnt/trunk/lnt/server/reporting/dailyreport.py @@ -64,6 +64,16 @@ if len(all_samples) > 0: self.min_sample = min(all_samples) self.max_sample = max(all_samples) + hashes = [] + for dr in self.day_results: + if dr is None: + hashes.append(None) + else: + hashes.append(dr.hash) + rgb_colors = util.get_rgb_colors_for_hashes(hashes) + for i, dr in enumerate(self.day_results): + if dr is not None: + dr.hash_rgb_color = rgb_colors[i] class DailyReport(object): Index: lnt/trunk/lnt/server/ui/templates/reporting/daily_report.html =================================================================== --- lnt/trunk/lnt/server/ui/templates/reporting/daily_report.html +++ lnt/trunk/lnt/server/ui/templates/reporting/daily_report.html @@ -156,15 +156,25 @@ {%- macro spark_x_coord(day_nr) -%} {{ (nr_days - day_nr) * x_day_spacing + x_border_size }} {%- endmacro -%} +{%- macro spark_hash_background(day_nr, dr) -%} + {%- if dr.cr.cur_hash is not none -%} + {%- set style = "fill: "+dr.hash_rgb_color+";" -%} + {%- else -%} + {%- set style = "fill: none;" -%} + {%- endif -%} + +{%- endmacro -%} - + {#- Make y-axis go upwards instead of downwards: #} {%- for dr in day_results -%} {%- if dr is not none and not dr.cr.failed -%} {%- set day_nr = loop.index %} {%- set nr_samples_for_day = dr.samples|length %} + {{ spark_hash_background(day_nr, dr) }} {%- for sample in dr.samples -%} {# fuzz the x-coordinate slightly so that multiple samples with the same value can be noticed #} Index: lnt/trunk/lnt/server/ui/util.py =================================================================== --- lnt/trunk/lnt/server/ui/util.py +++ lnt/trunk/lnt/server/ui/util.py @@ -62,6 +62,42 @@ v = .88 return colorsys.hsv_to_rgb(h,s,v) + +# The hash color palette avoids green and red as these colours are already used +# in quite a few places to indicate "good" or "bad". +hash_color_palette = ( + colorsys.hsv_to_rgb(h=45./360, s=0.3, v=0.9999), # warm yellow + colorsys.hsv_to_rgb(h=210./360, s=0.3, v=0.9999), # blue cyan + colorsys.hsv_to_rgb(h=300./360, s=0.3, v=0.9999), # mid magenta + colorsys.hsv_to_rgb(h=150./360, s=0.3, v=0.9999), # green cyan + colorsys.hsv_to_rgb(h=225./360, s=0.3, v=0.9999), # cool blue + colorsys.hsv_to_rgb(h=180./360, s=0.3, v=0.9999), # mid cyan +) + + +def get_rgb_colors_for_hashes(hash_strings): + hash2color = {} + unique_hash_counter = 0 + for hash_string in hash_strings: + if hash_string is not None: + if hash_string in hash2color: + continue + hash2color[hash_string] = hash_color_palette[unique_hash_counter] + unique_hash_counter += 1 + if unique_hash_counter >= len(hash_color_palette): + break + result = [] + for hash_string in hash_strings: + if hash_string is None: + result.append(None) + else: + # If not one of the first N hashes, return rgb value 0,0,0 which is + # white. + rgb = hash2color.get(hash_string, (0.999, 0.999, 0.999)) + result.append(toColorString(rgb)) + return result + + class multidict: def __init__(self, elts=()): self.data = {} Index: lnt/trunk/tests/SharedInputs/SmallInstance/data/lnt_db_create.sql =================================================================== --- lnt/trunk/tests/SharedInputs/SmallInstance/data/lnt_db_create.sql +++ lnt/trunk/tests/SharedInputs/SmallInstance/data/lnt_db_create.sql @@ -5,7 +5,7 @@ PRIMARY KEY ("Name"), UNIQUE ("Name") ); -INSERT INTO "SchemaVersion" VALUES('__core__',6); +INSERT INTO "SchemaVersion" VALUES('__core__',7); CREATE TABLE "TestSuite" ( "ID" INTEGER PRIMARY KEY NOT NULL, "Name" VARCHAR(256), @@ -31,6 +31,7 @@ ); INSERT INTO "SampleType" ("Name") VALUES('Real'); -- ID 1 INSERT INTO "SampleType" ("Name") VALUES('Status'); -- ID 2 +INSERT INTO "SampleType" ("Name") VALUES('Hash'); -- ID 3 CREATE TABLE "TestSuiteRunFields" ( "ID" INTEGER PRIMARY KEY NOT NULL, "TestSuiteID" INTEGER, @@ -109,6 +110,12 @@ INSERT INTO "TestSuiteSampleFields" ("TestSuiteID", "Name", "Type", "InfoKey", "status_field", "bigger_is_better") VALUES(1,'mem_bytes',1,'.mem',NULL,0); -- ID 16 +INSERT INTO "TestSuiteSampleFields" ("TestSuiteID", "Name", "Type", "InfoKey", + "status_field", "bigger_is_better") + VALUES(1,'hash_status',2,'.hash.status',NULL,0); -- ID 17 +INSERT INTO "TestSuiteSampleFields" ("TestSuiteID", "Name", "Type", "InfoKey", + "status_field", "bigger_is_better") + VALUES(1,'hash',3,'.hash',NULL,0); -- ID 18 CREATE TABLE "TestSuiteMachineFields" ( "ID" INTEGER PRIMARY KEY NOT NULL, "TestSuiteID" INTEGER, @@ -188,10 +195,12 @@ execution_status INTEGER, compile_time FLOAT, execution_time FLOAT, score FLOAT, "mem_bytes" FLOAT, + hash_status INTEGER, hash VARCHAR(32), FOREIGN KEY("RunID") REFERENCES "NT_Run" ("ID"), FOREIGN KEY("TestID") REFERENCES "NT_Test" ("ID"), FOREIGN KEY(compile_status) REFERENCES "StatusKind" ("ID"), - FOREIGN KEY(execution_status) REFERENCES "StatusKind" ("ID") + FOREIGN KEY(execution_status) REFERENCES "StatusKind" ("ID"), + FOREIGN KEY(hash_status) REFERENCES "StatusKind" ("ID") ); INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", "execution_status", "compile_time", "execution_time", Index: lnt/trunk/tests/server/ui/Inputs/V4Pages_extra_records.sql =================================================================== --- lnt/trunk/tests/server/ui/Inputs/V4Pages_extra_records.sql +++ lnt/trunk/tests/server/ui/Inputs/V4Pages_extra_records.sql @@ -99,5 +99,56 @@ VALUES(9,6,0,0,0.001,1.2,NULL,NULL); -- ID 11: passing result; 20% bigger, -- so shown in daily report page. +-- check background colors being produced correctly, corresponding to recorded +-- hashes of the binary. +INSERT INTO "NT_Test" VALUES(7,'test_hash1'); -- ID 7 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(7,7,0,0,0.001,1.0,NULL,NULL,0,'hash1'); -- ID 11: hash1 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(8,7,0,0,0.001,1.0,NULL,NULL,NULL,NULL); -- ID 12: no hash +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(9,7,0,0,0.001,1.2,NULL,NULL,0,'hash2'); -- ID 13: hash2; 20% bigger, + -- so shown in daily report page. + +INSERT INTO "NT_Test" VALUES(8,'test_hash2'); -- ID 8 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(7,8,0,0,0.001,1.0,NULL,NULL,0,'hash1'); -- ID 14: hash1 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(8,8,0,0,0.001,1.0,NULL,NULL,0,'hash2'); -- ID 15: hash2 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(9,8,0,0,0.001,1.2,NULL,NULL,0,'hash1'); -- ID 16: hash1; 20% bigger, + -- so shown in daily report page. + +INSERT INTO "NT_Test" VALUES(9,'test_mhash_on_run'); -- ID 9 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(7,9,0,0,0.001,1.0,NULL,NULL,0,'hash1'); -- ID 15: hash1 +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(7,9,0,0,0.001,1.0,NULL,NULL,0,'hash2'); -- ID 16: hash2, same day +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(8,9,0,0,0.001,1.0,NULL,NULL,1,NULL); -- ID 17: no hash the next day +INSERT INTO "NT_Sample" ("RunID", "TestID", "compile_status", + "execution_status", "compile_time", "execution_time", + "score", "mem_bytes", "hash_status", "hash") + VALUES(9,9,0,0,0.001,1.2,NULL,NULL,0,'hash3'); -- ID 18: hash3; 20% bigger, + -- so shown in daily report page. + COMMIT; Index: lnt/trunk/tests/server/ui/V4Pages.py =================================================================== --- lnt/trunk/tests/server/ui/V4Pages.py +++ lnt/trunk/tests/server/ui/V4Pages.py @@ -139,8 +139,7 @@ check_table_content(table, expected_content) -def get_sparkline(client, url, fieldname, testname, machinename): - table = get_results_table(client, url, fieldname) +def get_sparkline(table, testname, machinename): body_content = [[cell for cell in row.findall("./td")] for row in table.findall("./tbody/tr")] @@ -166,6 +165,33 @@ return samples +fillStyleRegex = re.compile("fill: *(?P[^;]+);") + + +def extract_background_colors(sparkline_svg, nr_days): + rects = sparkline_svg.findall(".//rect") + # The first rectangle returned is the default background, so remove that + # one. + assert len(rects) >= 1 + rects = rects[1:] + result = [] + for rect in rects: + style = rect.get("style", None) + if style is None: + result.append(None) + continue + m = fillStyleRegex.search(style) + if m is None: + result.append(None) + continue + fill = m.group('fill') + if fill == 'none': + result.append(None) + else: + result.append(fill) + return result + + def main(): _, instance_path = sys.argv @@ -289,11 +315,17 @@ '/v4/nts/daily_report/2012/5/13?num_days=3', "execution_time", [["test6", ""], - ["", "machine2", "1.0000", "FAIL", "PASS", ""]]) - sparkline_xml = get_sparkline(client, - '/v4/nts/daily_report/2012/5/13?num_days=3', - "execution_time", "test6", "machine2") - nr_sample_points = len(extract_sample_points(sparkline_xml)) + ["", "machine2", "1.0000", "FAIL", "PASS", ""], + ["test_hash1", ""], + ["", "machine2", "1.0000", '-', '20.00%', ""], + ["test_hash2", ""], + ["", "machine2", "1.0000", '-', '20.00%', ""], + ["test_mhash_on_run", ""], + ["", "machine2", "1.0000", '-', '20.00%', ""], ]) + result_table = get_results_table( + client, '/v4/nts/daily_report/2012/5/13?num_days=3', "execution_time") + sparkline_test6_xml = get_sparkline(result_table, "test6", "machine2") + nr_sample_points = len(extract_sample_points(sparkline_test6_xml)) assert 2 == nr_sample_points, \ "Expected 2 sample points, found %d" % nr_sample_points @@ -301,8 +333,52 @@ client, '/v4/nts/daily_report/2012/5/04', [['machine2', '2', '0', '1']]) - - + # Check that a different background color is used in the sparkline + # when the hash values recorded are different. At the same time, + # check that no background color is drawn on missing hash values, + # using a sequence of (hash1, no hash, hash2) over 3 consecutive + # days. + sparkline_hash1_xml = get_sparkline(result_table, "test_hash1", "machine2") + nr_sample_points = len(extract_sample_points(sparkline_hash1_xml)) + assert 3 == nr_sample_points, \ + "Expected 3 sample points, found %d" % nr_sample_points + background_colors = extract_background_colors(sparkline_hash1_xml, 3) + assert len(background_colors) == 3 + color1, color2, color3 = background_colors + assert color1 is not None + assert color3 is not None + assert color1 != color3 + assert color2 is None + + # Check that the same background color is used in the sparkline + # when the hash values recorded are the same, using a + # (hash1, hash2, hash1) sequence. + sparkline_hash2_xml = get_sparkline(result_table, "test_hash2", "machine2") + nr_sample_points = len(extract_sample_points(sparkline_hash2_xml)) + assert 3 == nr_sample_points, \ + "Expected 3 sample points, found %d" % nr_sample_points + background_colors = extract_background_colors(sparkline_hash2_xml, 3) + assert len(background_colors) == 3 + color1, color2, color3 = background_colors + assert color1 is not None + assert color1 == color3 + assert color1 != color2 + assert color2 is not None + + # Check that we don't crash if a single run produces multiple + # samples with different hash values for the same run. This could + # happen e.g. when the compiler under test doesn't produce + # object code deterministically. + sparkline_mhashonrun_xml = get_sparkline( + result_table, "test_mhash_on_run", "machine2") + nr_sample_points = len(extract_sample_points(sparkline_mhashonrun_xml)) + assert 4 == nr_sample_points, \ + "Expected 4 sample points, found %d" % nr_sample_points + background_colors = extract_background_colors(sparkline_mhashonrun_xml, 3) + assert len(background_colors) == 3 + color1, color2, color3 = background_colors + assert color2 is None + assert color1 != color3 # Now check the compile report # Get the V4 overview page.