| 1 |
{% extends "base.html" %} |
| 2 |
{% import "repo_tabs.html" as m %} |
| 3 |
{% block title %}Tests · {{ repo.owner }}/{{ repo.name }}{% endblock %} |
| 4 |
{% block tabs %}{% call m::tabs(repo, "tests") %}{% endblock %} |
| 5 |
{% block content %} |
| 6 |
{% if let Some(r) = report %} |
| 7 |
<section class="cov-hero"> |
| 8 |
<div class="cov-hero-head"> |
| 9 |
<div> |
| 10 |
<h1>Tests</h1> |
| 11 |
<div class="cov-sub">{% if bookmarks.len() > 1 %}<form class="inline" method="get" action="{{ repo.url }}/tests"><label for="t-bm" class="mute">bookmark</label> <select id="t-bm" name="bookmark" onchange="this.form.submit()">{% for b in bookmarks %}<option value="{{ b }}"{% if b.as_str() == bookmark.as_str() %} selected{% endif %}>{{ b }}</option>{% endfor %}</select> <noscript><button>Show</button></noscript></form>{% else %}<span class="mute">on</span> <strong>{{ bookmark }}</strong>{% endif %} |
| 12 |
· <a href="{{ repo.url }}/flow/{{ r.run_number }}">run #{{ r.run_number }}</a> · job {{ r.job }} · <time title="{{ r.created_ms|when_i }}">{{ r.created_ms|ago_i }}</time></div> |
| 13 |
</div> |
| 14 |
<div class="cov-score {{ self.class_of(r) }}"> |
| 15 |
<div class="cov-big">{% if r.broken() > 0 %}{{ r.broken() }} failing{% else %}{{ self.rate(r) }}{% endif %}</div> |
| 16 |
<div class="cov-change same">{% if r.broken() > 0 %}{{ self.rate(r) }} of {{ r.tests - r.skipped }} passed{% else %}of {{ r.tests - r.skipped }} tests passed{% endif %}</div> |
| 17 |
</div> |
| 18 |
</div> |
| 19 |
{% if runs > 1 %}<div class="cov-hero-chart">{{ rate_chart|safe }}</div>{% endif %} |
| 20 |
</section> |
| 21 |
|
| 22 |
<section class="cov-facts"> |
| 23 |
<div><span class="k">Tests</span><span class="v">{{ r.tests }}{% if let Some(d) = self.count_delta() %} <small>{{ self.signed(d) }}</small>{% endif %}</span></div> |
| 24 |
<div><span class="k">Passed</span><span class="v cov-good">{{ r.passed }}</span></div> |
| 25 |
<div><span class="k">Failed</span><span class="v{% if r.failed > 0 %} cov-poor{% endif %}">{{ r.failed }}</span></div> |
| 26 |
{% if r.errors > 0 %}<div><span class="k">Errored</span><span class="v cov-poor">{{ r.errors }}</span></div>{% endif %} |
| 27 |
<div><span class="k">Skipped</span><span class="v">{{ r.skipped }}</span></div> |
| 28 |
<div><span class="k">Test time</span><span class="v">{{ self.time(r.time_ms) }}{% if let Some(d) = self.time_delta() %} <small>{{ d }}</small>{% endif %}</span></div> |
| 29 |
{% if let Some(t) = first_ms %}<div><span class="k">Reporting since</span><span class="v small"><time title="{{ t|when_i }}">{{ t|ago_i }}</time></span></div>{% endif %} |
| 30 |
</section> |
| 31 |
|
| 32 |
<div class="cov-grid"> |
| 33 |
<section class="panel cov-card"> |
| 34 |
<h2>This run</h2> |
| 35 |
<ul class="cov-stats"> |
| 36 |
<li><strong>{{ r.passed }}</strong> of <strong>{{ r.tests - r.skipped }}</strong> tests passed <span class="{{ self.class_of(r) }}">({{ self.rate(r) }})</span>{% if r.skipped > 0 %}, {{ r.skipped }} skipped{% endif %}</li> |
| 37 |
{% if let Some(p) = previous %}<li>Against <a href="{{ repo.url }}/tests?report={{ p.id }}">run #{{ p.run_number }}</a>: {{ broke.len() }} newly failing, {{ fixed.len() }} fixed{% if added > 0 %}, {{ added }} new tests{% endif %}</li>{% else %}<li>The first report of job {{ r.job }} here</li>{% endif %} |
| 38 |
</ul> |
| 39 |
{% if !broke.is_empty() %}<h3>Newly failing</h3> |
| 40 |
<ul class="t-list">{% for c in broke %}<li><span class="cov-poor">✕</span> <code>{{ c.title }}</code></li>{% endfor %}</ul>{% endif %} |
| 41 |
{% if !fixed.is_empty() %}<h3>Fixed</h3> |
| 42 |
<ul class="t-list">{% for c in fixed %}<li><span class="cov-good">✓</span> <code>{{ c.title }}</code></li>{% endfor %}</ul>{% endif %} |
| 43 |
</section> |
| 44 |
<section class="panel cov-card"> |
| 45 |
<h2>Flaky <small class="mute">failed and passed in the last {{ flaky_runs }} runs</small></h2> |
| 46 |
{% if flaky.is_empty() %}<p class="mute small">None: every test kept its result.</p>{% else %} |
| 47 |
<table class="cov-moved">{% for f in flaky %}<tr><td><a href="{{ repo.url }}/tests/case?classname={{ f.classname|urlencode }}&name={{ f.name|urlencode }}&bookmark={{ bookmark|urlencode }}"><code>{{ f.title() }}</code></a></td><td class="num"><span class="cov-poor">{{ f.failed }} ✕</span> <span class="cov-good">{{ f.passed }} ✓</span></td></tr>{% endfor %}</table>{% endif %} |
| 48 |
</section> |
| 49 |
</div> |
| 50 |
|
| 51 |
{% if !failures.is_empty() %}<section class="panel"> |
| 52 |
<h2>Failures <small class="mute">{{ r.broken() }} in run #{{ r.run_number }}{% if r.broken() > failures.len() as i64 %}, the first {{ failures.len() }} shown{% endif %}</small></h2> |
| 53 |
<div class="flow-tests">{% for f in failures %}<details class="tests-failure"> |
| 54 |
<summary><span class="bad">{% if f.status == "error" %}!{% else %}✕{% endif %}</span> <code>{{ f.title() }}</code> <a class="small" href="{{ repo.url }}/tests/case?classname={{ f.classname|urlencode }}&name={{ f.name|urlencode }}&bookmark={{ bookmark|urlencode }}">history</a>{% if !f.message.is_empty() %} <span class="small mute">{{ f.message }}</span>{% endif %}</summary> |
| 55 |
{% if !f.output.is_empty() %}<pre class="flow-log">{{ f.output }}</pre>{% endif %} |
| 56 |
</details>{% endfor %}</div> |
| 57 |
</section>{% endif %} |
| 58 |
|
| 59 |
{% if runs > 1 %}<section class="panel"> |
| 60 |
<h2>Tests run <small class="mute"><span class="cov-key covered">passed</span> <span class="cov-key relevant">failed</span></small></h2> |
| 61 |
{{ count_chart|safe }} |
| 62 |
</section>{% endif %} |
| 63 |
|
| 64 |
<section class="panel"> |
| 65 |
<h2>Slowest tests <small class="mute">in run #{{ r.run_number }}</small></h2> |
| 66 |
<div class="table"><table class="cov-files"> |
| 67 |
<tr><th>Test</th><th class="num">Time</th></tr> |
| 68 |
{% for c in slowest %}<tr><td><a href="{{ repo.url }}/tests/case?classname={{ c.classname|urlencode }}&name={{ c.name|urlencode }}&bookmark={{ bookmark|urlencode }}"><code>{{ c.title() }}</code></a>{% if c.status != "passed" %} <span class="cov-poor small">{{ c.status }}</span>{% endif %}</td><td class="num">{{ self.time(c.time_ms) }}</td></tr>{% endfor %} |
| 69 |
</table></div> |
| 70 |
</section> |
| 71 |
|
| 72 |
{% if recent.len() > 1 %}<section class="panel"> |
| 73 |
<h2>Recent runs on {{ bookmark }}</h2> |
| 74 |
<div class="table"><table class="cov-runs"> |
| 75 |
<tr><th>Run</th><th>Job</th><th>Passing</th><th class="num">Tests</th><th class="num">Failed</th><th class="num">Time</th><th>When</th></tr> |
| 76 |
{% for x in recent %}<tr{% if x.report.id == r.id %} class="on"{% endif %}> |
| 77 |
<td><a href="{{ repo.url }}/tests?report={{ x.report.id }}">#{{ x.report.run_number }}</a></td> |
| 78 |
<td>{{ x.report.job }}</td> |
| 79 |
<td><span class="cov-bar {{ self.class_of(x.report) }}"><span style="width: {% if let Some(p) = crate::web::test_reports::pass_rate(x.report) %}{{ p }}{% else %}0{% endif %}%"></span></span> {{ self.rate(x.report) }}</td> |
| 80 |
<td class="num">{{ x.report.tests }}{% if let Some(d) = x.delta %}{% if *d != 0 %} <small class="mute">{{ self.signed(d) }}</small>{% endif %}{% endif %}</td> |
| 81 |
<td class="num">{% if x.report.broken() > 0 %}<span class="cov-poor">{{ x.report.broken() }}</span>{% else %}0{% endif %}</td> |
| 82 |
<td class="num">{{ self.time(x.report.time_ms) }}</td> |
| 83 |
<td class="small"><time title="{{ x.report.created_ms|when_i }}">{{ x.report.created_ms|ago_i }}</time></td> |
| 84 |
</tr>{% endfor %} |
| 85 |
</table></div> |
| 86 |
</section>{% endif %} |
| 87 |
{% else %} |
| 88 |
<div class="head"><h1>Tests <small>of {{ bookmark }}</small></h1></div> |
| 89 |
<section class="panel"> |
| 90 |
<p>No test report for <strong>{{ bookmark }}</strong> yet.</p> |
| 91 |
<p>Give a job in <code>.flow/pipeline.kdl</code> an artifact named <code>junit</code> holding a JUnit XML report, kept with <code>when "always"</code> so a failing run still sends it. For pytest:</p> |
| 92 |
<pre>job "test" { |
| 93 |
sandbox "python" |
| 94 |
exec "pytest" "--junitxml=/src/out/junit.xml" |
| 95 |
output { |
| 96 |
artifact "junit" { |
| 97 |
path "/src/out/junit.xml" |
| 98 |
when "always" |
| 99 |
} |
| 100 |
} |
| 101 |
}</pre> |
| 102 |
<p class="small mute">cargo-nextest (a <code>junit</code> profile), Jest's junit reporter and gotestsum write JUnit XML too.</p> |
| 103 |
</section> |
| 104 |
{% endif %} |
| 105 |
{% endblock %} |