Skip to content

Commit

Permalink
Extend Continuous Testing page by tags.
Browse files Browse the repository at this point in the history
- introduce `tags` column
- introduce toggle to hide the `tags`column
- automatically hide `tags` column when JUnit tags are not used
  • Loading branch information
ueberfuhr committed Sep 6, 2024
1 parent bd33359 commit b73905e
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,7 @@
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.platform.commons.annotation.Testable;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.*;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.engine.support.descriptor.ClassSource;
Expand Down Expand Up @@ -258,8 +254,9 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) {
if (testClass != null) {
Map<UniqueId, TestResult> results = resultsByClass.computeIfAbsent(testClass.getName(),
s -> new HashMap<>());
TestResult result = new TestResult(displayName, testClass.getName(), id,
TestExecutionResult.aborted(null),
TestResult result = new TestResult(displayName, testClass.getName(),
toTagList(testIdentifier),
id, TestExecutionResult.aborted(null),
logHandler.captureOutput(), testIdentifier.isTest(), runId, 0, true);
results.put(id, result);
if (result.isTest()) {
Expand Down Expand Up @@ -312,8 +309,9 @@ public void executionFinished(TestIdentifier testIdentifier,
}
Map<UniqueId, TestResult> results = resultsByClass.computeIfAbsent(testClassName,
s -> new HashMap<>());
TestResult result = new TestResult(displayName, testClassName, id,
testExecutionResult,
TestResult result = new TestResult(displayName, testClassName,
toTagList(testIdentifier),
id, testExecutionResult,
logHandler.captureOutput(), testIdentifier.isTest(), runId,
System.currentTimeMillis() - startTimes.get(testIdentifier), true);
if (!results.containsKey(id)) {
Expand All @@ -332,6 +330,7 @@ public void executionFinished(TestIdentifier testIdentifier,
results.put(id,
new TestResult(currentNonDynamicTest.get().getDisplayName(),
result.getTestClass(),
toTagList(testIdentifier),
currentNonDynamicTest.get().getUniqueIdObject(),
TestExecutionResult.failed(failure), List.of(), false, runId, 0,
false));
Expand All @@ -349,6 +348,7 @@ public void executionFinished(TestIdentifier testIdentifier,
for (TestIdentifier child : children) {
UniqueId childId = UniqueId.parse(child.getUniqueId());
result = new TestResult(child.getDisplayName(), testClassName,
toTagList(testIdentifier),
childId,
testExecutionResult,
logHandler.captureOutput(), child.isTest(), runId,
Expand Down Expand Up @@ -419,6 +419,15 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e
}
}

private static List<String> toTagList(TestIdentifier testIdentifier) {
return testIdentifier
.getTags()
.stream()
.map(TestTag::getName)
.sorted()
.toList();
}

private Class<?> getTestClassFromSource(Optional<TestSource> optionalTestSource) {
if (optionalTestSource.isPresent()) {
var testSource = optionalTestSource.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class TestResult {

final String displayName;
final String testClass;
final List<String> tags;
final UniqueId uniqueId;
final TestExecutionResult testExecutionResult;
final List<String> logOutput;
Expand All @@ -20,10 +21,12 @@ public class TestResult {
final List<Throwable> problems;
final boolean reportable;

public TestResult(String displayName, String testClass, UniqueId uniqueId, TestExecutionResult testExecutionResult,
public TestResult(String displayName, String testClass, List<String> tags, UniqueId uniqueId,
TestExecutionResult testExecutionResult,
List<String> logOutput, boolean test, long runId, long time, boolean reportable) {
this.displayName = displayName;
this.testClass = testClass;
this.tags = tags;
this.uniqueId = uniqueId;
this.testExecutionResult = testExecutionResult;
this.logOutput = logOutput;
Expand Down Expand Up @@ -58,6 +61,10 @@ public String getTestClass() {
return testClass;
}

public List<String> getTags() {
return tags;
}

public UniqueId getUniqueId() {
return uniqueId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
_state: {state: true},
_results: {state: true},
_busy: {state: true},
_detailsOpenedItem: {state: true, type: Array}
_detailsOpenedItem: {state: true, type: Array},
_displayTags: {state: true, type: Boolean},
};

constructor() {
Expand All @@ -89,6 +90,7 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
this._detailsOpenedItem = [];
this._chartTitles = ["passed", "failed", "skipped"];
this._chartColors = ['--lumo-success-text-color', '--lumo-error-text-color', '--lumo-contrast-70pct'];
this._displayTags = true;
}

connectedCallback() {
Expand Down Expand Up @@ -135,21 +137,64 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
}

render() {
let results = this._prepareResultsToRender();
return html`
${this._renderMenuBar()}
${this._renderResultSet()}
${this._renderMenuBar(results)}
${this._renderResults(results)}
${this._renderBarChart()}
`;
}

_renderMenuBar(){
_prepareResultsToRender() {
if(this._state && this._state.running && this._results && this._results.results) {
let itemsByState = {
passing: [],
failing: [],
skipped: [],
}
Object
.values(this._results.results)
.forEach(item => {
itemsByState.passing.push(...item.passing.filter( obj => obj.test === true ));
itemsByState.failing.push(...item.failing.filter( obj => obj.test === true ));
itemsByState.skipped.push(...item.skipped.filter( obj => obj.test === true ));
});
let items = itemsByState.failing.concat(
itemsByState.passing,
itemsByState.skipped
);
let hasTags = items.find( item => item.tags && item.tags.length > 0 );
return {
items: items,
meta: {
hasTags: hasTags,
failing: itemsByState.failing.length,
passing: itemsByState.passing.length,
skipped: itemsByState.skipped.length,
},
}
} else {
return {
items: [],
meta: {
hasTags: false,
failing: 0,
passing: 0,
skipped: 0,
},
};
}
}

_renderMenuBar(results){
if(this._state){
return html`<div class="menubar">
<div>
${this._renderStopStartButton()}
${this._renderRunAllButton()}
${this._renderRunFailedButton()}
${this._renderToggleBrokenOnly()}
${this._renderToggleDisplayTags(results)}
</div>
${this._renderBusyIndicator()}
</div>`;
Expand Down Expand Up @@ -193,62 +238,28 @@ export class QwcContinuousTesting extends QwcHotReloadElement {

}

_renderResultSet(){
if(this._state && this._state.running && this._results && this._results.results) {

let failingResults = this._results.failing;
let passingResults = this._results.passing;
let skippedResults = this._results.skipped;

var allResults = failingResults.concat(passingResults, skippedResults);

return html`${this._renderResults(allResults)}`;
}

}

_renderResults(results){
if(results.length > 0){
if(results.items.length > 0){

return html`
<vaadin-grid .items="${results.items}" class="resultTable" theme="no-border"
.detailsOpenedItems="${this._detailsOpenedItem}"
@active-item-changed="${(event) => {
const prop = event.detail.value;
this._detailsOpenedItem = prop ? [prop] : [];
}}"
${gridRowDetailsRenderer(this._descriptionRenderer, [])}
>
${
this._displayTags && results.meta.hasTags
? html`<vaadin-grid-sort-column path="tags" header="Tags" ${columnBodyRenderer((prop) => this._tagsRenderer(prop), [])}></vaadin-grid-sort-column>`
: ''
}
<vaadin-grid-sort-column path="testClass" header="Test Class" ${columnBodyRenderer((prop) => this._testRenderer(prop), [])}></vaadin-grid-sort-column>
<vaadin-grid-sort-column path="displayName" header="Name" ${columnBodyRenderer((prop) => this._nameRenderer(prop), [])}></vaadin-grid-sort-column>
<vaadin-grid-sort-column path="time" header="Time" ${columnBodyRenderer((prop) => this._timeRenderer(prop), [])}>></vaadin-grid-sort-column>
</vaadin-grid>`;

let items = [];

for (let i = 0; i < results.length; i++) {
let result = results[i];

let failingResult = result.failing.filter(function( obj ) {
return obj.test === true;
});
let passingResult = result.passing.filter(function( obj ) {
return obj.test === true;
});
let skippedResult = result.skipped.filter(function( obj ) {
return obj.test === true;
});

items.push.apply(items, failingResult);
items.push.apply(items, passingResult);
items.push.apply(items, skippedResult);
}

if(items.length>0){
return html`
<vaadin-grid .items="${items}" class="resultTable" theme="no-border"
.detailsOpenedItems="${this._detailsOpenedItem}"
@active-item-changed="${(event) => {
const prop = event.detail.value;
this._detailsOpenedItem = prop ? [prop] : [];
}}"
${gridRowDetailsRenderer(this._descriptionRenderer, [])}
>
<vaadin-grid-sort-column path="testClass" header="Test Class" ${columnBodyRenderer((prop) => this._testRenderer(prop), [])}></vaadin-grid-sort-column>
<vaadin-grid-sort-column path="displayName" header="Name" ${columnBodyRenderer((prop) => this._nameRenderer(prop), [])}></vaadin-grid-sort-column>
<vaadin-grid-sort-column path="time" header="Time" ${columnBodyRenderer((prop) => this._timeRenderer(prop), [])}>></vaadin-grid-sort-column>
</vaadin-grid>`;
}else{
return html`No tests`;
}

}else{
return html`No tests`;
}
Expand Down Expand Up @@ -286,6 +297,35 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
)}`;
}

_tagToColor(tag){
// Step 0: two strings with the last char differing by 1 should render to totally different colors
const tagValue = tag + tag;
// Step 1: Convert the string to a numeric hash value
let hash = 0;
for (let i = 0; i < tagValue.length; i++) {
hash = tagValue.charCodeAt(i) + ((hash << 5) - hash);
}

// Step 2: Convert the numeric hash value to a hex color code
let color = '#';
const normalizeFactor = 0.2; // cut 20% light and dark values
for (let i = 0; i < 3; i++) {
const value = Math.round(((hash >> (i * 8)) & 0xFF) * (1-2*normalizeFactor) + 255*normalizeFactor);
color += ('00' + value.toString(16)).slice(-2);
}

return color;
}

_tagsRenderer(testLine){
return html`${testLine.tags.map((tag, index) => {
const color = this._tagToColor(tag);
return html`<qui-badge small pill color="${color}" background="${color}40">
<span>${"io.quarkus.test.junit.QuarkusTest" === tag ? "Q" : tag}</span>
</qui-badge> `;
})}`;
}

_testRenderer(testLine){
let level = testLine.testExecutionResult.status.toLowerCase();

Expand Down Expand Up @@ -369,6 +409,17 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
}
}

_renderToggleDisplayTags(results) {
if(this._state && this._state.running){
return html`<vaadin-checkbox id="display-tags-cnt-testing-chk" theme="small"
@change="${this._toggleDisplayTags}"
?checked=${this._displayTags}
?disabled=${this._state.inProgress || this._busy || !results.meta.hasTags}
label="Display tags (if available)">
</vaadin-checkbox>`;
}
}

_start(){
if(!this._busy){
this._busy = true;
Expand Down Expand Up @@ -407,5 +458,9 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
this._busy = false;
});
}

_toggleDisplayTags(){
this._displayTags = !this._displayTags;
}
}
customElements.define('qwc-continuous-testing', QwcContinuousTesting);

0 comments on commit b73905e

Please sign in to comment.