Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display JUnit Tags in Dev UI (Continuous Testing) #42988

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.TestTag;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.reporting.ReportEntry;
Expand Down Expand Up @@ -258,8 +259,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 +314,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 +335,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 +353,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 +424,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);
Loading