Skip to content

Commit

Permalink
feat: show output from test runs failing with error (#13)
Browse files Browse the repository at this point in the history
Introduces a view for test results that fail because of an error which
allows the user to inspect the test command output and exit code for
debugging purposes.

Closes #12
  • Loading branch information
michenriksen authored May 18, 2023
1 parent 15bb3ae commit ed17813
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 156 deletions.
4 changes: 2 additions & 2 deletions internal/pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Result struct {
Start time.Time `json:"start"`
Duration time.Duration `json:"duration"`
ExitCode int `json:"exitCode"`
Output []byte `json:"output"`
Targets []string `json:"targets"`
Passed int `json:"passed"`
Failed int `json:"failed"`
Expand Down Expand Up @@ -168,6 +169,7 @@ func (r *Runner) Run(pkgs ...string) (*Result, error) {
if err := r.handleError(err, out); err != nil {
result.Error = err.Error()
result.Pass = false
result.Output = out

return result, nil
}
Expand Down Expand Up @@ -252,8 +254,6 @@ func (r *Runner) handleError(err error, out []byte) error {
reason = "timeout"
case errors.Is(err, exec.ErrNotFound):
reason = fmt.Sprintf("go binary %q was not found", r.goBin)
case bytes.Contains(out, []byte("panic: test timed out")):
reason = "timeout"
case bytes.Contains(out, []byte("[build failed]")):
reason = "build failed"
default:
Expand Down
7 changes: 1 addition & 6 deletions internal/pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,7 @@ func (s *Server) Close() error {
// Prepends the result to the internal result slice. Slice is trimmed if its
// length exceeds configured max results.
func (s *Server) AddResult(r *runner.Result) {
if r.Error != "" {
s.sendClientMessage(newClientMessage("resultError", r))
return
}

if r.Tests == 0 {
if r.Error == "" && r.Tests == 0 {
s.sendClientMessage(newClientMessage("resultEmpty", r))
return
}
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TestServerWebsocketResultError(t *testing.T) {
serverInstance.AddResult(&runner.Result{UUID: "deadbeef", Error: "warp core breach"})
msg := waitForMessage(t, ws)

require.Equal(t, "resultError", msg.Kind)
require.Equal(t, "result", msg.Kind)

result, ok := msg.Data.(map[string]any)
require.True(t, ok)
Expand Down
1 change: 1 addition & 0 deletions web/app/src/lib/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface Result {
start: string;
duration: number;
exitCode: number;
output: string;
targets: string[];
passed: number;
failed: number;
Expand Down
125 changes: 125 additions & 0 deletions web/app/src/lib/components/ResultEmptyState.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { mdiLightbulbOutline } from '@mdi/js';
import { dispatchRunTests } from '$lib/stores/events';
import { state } from '$lib/stores/status';
import SvgIcon from '$lib/components/SvgIcon.svelte';
let animatePlaceholders = false;
const unsubscribe = state.subscribe((newState) => {
if (newState === 'running') {
animatePlaceholders = true;
}
});
onDestroy(() => unsubscribe());
</script>

<div class="logo-cloud grid-cols-1 lg:!grid-cols-5 gap-1 mb-6" class:animate-pulse={animatePlaceholders}>
<div class="logo-item">
<span><div class="placeholder-circle w-10" /></span>
<span><div class="placeholder w-20" /></span>
</div>
<div class="logo-item">
<span><div class="placeholder-circle w-10" /></span>
<span><div class="placeholder w-20" /></span>
</div>
<div class="logo-item">
<span><div class="placeholder-circle w-10" /></span>
<span><div class="placeholder w-20" /></span>
</div>
<div class="logo-item">
<span><div class="placeholder-circle w-10" /></span>
<span><div class="placeholder w-20" /></span>
</div>
<div class="logo-item">
<span><div class="placeholder-circle w-10" /></span>
<span><div class="placeholder w-20" /></span>
</div>
</div>

<div class="card p-4 mb-6 flex-col space-y-6 overflow-x-clip" class:animate-pulse={animatePlaceholders}>
<div class="flex flex-col space-y-6 lg:flex-row lg:space-x-6 lg:space-y-0 items-center">
<div class="w-full lg:w-1/2 placeholder h-10" />
<div
class="w-full lg:w-1/2 flex flex-row space-x-4 justify-items-stretch justify-end lg:justify-items-end items-center"
>
<div class="placeholder w-48 h-10" />
<div class="placeholder w-48 h-10" />
<div class="placeholder w-48 h-10" />
<div class="placeholder-circle w-10" />
</div>
</div>
</div>

<div class="mb-10" class:animate-pulse={animatePlaceholders}>
<div class="card card-variant-surface p-4 flex flex-row items-center space-x-4 justify-between rounded-b-none">
<div class="flex-none">
<div class="placeholder-circle w-7" />
</div>

<div class="grow">
<div class="placeholder w-[60%]" />
</div>

<div class="flex flex-row space-x-1 items-center hidden md:inline-block">
<div class="placeholder w-8 inline-block" />
<div class="placeholder w-8 inline-block" />
<div class="placeholder w-9 inline-block" />
</div>
</div>

<div>
<div class="sticky top-0 py-2 shadow-md font-semibold">
<div class="flex flex-row space-x-4 items-center justify-center">
<div class="placeholder w-[200px]" />
</div>
</div>

{#each Array(5) as _}
{@const titleW = Math.floor(Math.random() * 30) + 10}

<div class="divide-y divide-surface-700">
<div class="flex flex-row items-center space-x-4 px-4 py-2 justify-between">
<div class="flex-none">
<div class="placeholder-circle w-7" />
</div>

<div class="grow max-w-md md:max-w-none overflow-x-hidden">
<div class="placeholder" style="width: {titleW}%" />
</div>

<div class="placeholder w-12" />
</div>
</div>
{/each}

<div class="flex flex-row p-4 items-center justify-end space-x-4 card rounded-t-none text-sm">
<div class="placeholder w-[50px]" />
<div class="placeholder w-[50px]" />
<div class="placeholder w-[50px]" />
<div class="placeholder w-[50px]" />
</div>
</div>
</div>

<div class="flex flex-col space-y-6 italic text-surface-400 items-center">
<div class="text-center">
<div class="text-lg font-semibold">No test results are available at the moment</div>
<p>
To view results, either modify a Go source file within your project or
<a href="/" class="!text-surface-300" on:click|preventDefault={() => dispatchRunTests('./...')}>run all tests</a>.
</p>
</div>

<div class="text-sm">
<span class="text-surface-300">
<SvgIcon path={mdiLightbulbOutline} size="1.2em" class="inline" />
Tip:
</span>
you can activate automatic run of all tests on page load in
<a href="/" class="!text-inherit" on:click|preventDefault={() => dispatchOpenSettings()}>the settings</a>.
</div>
</div>
2 changes: 1 addition & 1 deletion web/app/src/lib/components/ResultListBoxItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
{:else if result.error}
{#if result.error == 'timeout'}
<SvgIcon path={mdiTimerRemoveOutline} class="text-error-500" size={iconSize} />
{:else}j
{:else}
<SvgIcon path={mdiAlertCircleOutline} class="text-error-500" size={iconSize} />
{/if}
{:else}
Expand Down
4 changes: 2 additions & 2 deletions web/app/src/lib/services/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export class WSMessageManager {

private handleResultError(message: WSMessage) {
this.log(`received resultError message`);
state.set('ready');

if (!message.data) {
this.log('resultError message missing data, ignoring');
Expand All @@ -162,11 +163,11 @@ export class WSMessageManager {

NotificationService.notify(`Tests failed with error: ${result.error}`, 'error', result.uuid);
setPageTitle(`✖ ERROR: ${result.error}`);
state.set('ready');
}

private handleResultEmpty(message: WSMessage) {
this.log(`received resultEmpty message`);
state.set('ready');

if (!message.data) {
this.log('resultEmpty message missing data, ignoring');
Expand All @@ -181,7 +182,6 @@ export class WSMessageManager {
result.uuid
);
setPageTitle('? NO TESTS FOUND');
state.set('ready');
}

private handleNotification(message: WSMessage) {
Expand Down
39 changes: 22 additions & 17 deletions web/app/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import { WSMessageManager } from '$lib/services/websocket';
import { pluralize, setPageTitle } from '$lib/common/utils';
import type { State } from '$lib/common/types';
import type { NotificationType, State } from '$lib/common/types';
storePopup.set({ computePosition, autoUpdate, flip, shift, offset });
Expand Down Expand Up @@ -111,26 +111,31 @@
return;
}
let message = '';
let type: NotificationType = 'error';
let title = '';
if (newResult.pass) {
NotificationService.notify(
`Ran ${pluralize(newResult.tests, 'test', 'tests')} with 0 failures.`,
'pass',
newResult.uuid
);
setPageTitle(`✓ PASS: ${pluralize(newResult.tests, 'test', 'tests')}`);
message = `Ran ${pluralize(newResult.tests, 'test', 'tests')} with 0 failures.`;
type = 'pass';
title = `✓ PASS: ${pluralize(newResult.tests, 'test', 'tests')}`;
} else if (newResult.error !== '') {
const isTimeout = newResult.error === 'timeout';
message = isTimeout ? 'Test run timed out.' : `Test run failed with error: ${newResult.error}`;
title = isTimeout ? '✖ TIMEOUT: Test run timed out.' : `✖ ERROR: ${newResult.error}`;
} else {
NotificationService.notify(
`Ran ${pluralize(newResult.tests, 'test', 'tests')} with ${pluralize(
newResult.failed,
'failure',
'failures'
)}.`,
'fail',
newResult.uuid
);
setPageTitle(`✖ FAIL: ${pluralize(newResult.failed, 'failure', 'failures')}`);
message = `Ran ${pluralize(newResult.tests, 'test', 'tests')} with ${pluralize(
newResult.failed,
'failure',
'failures'
)}.`;
type = 'fail';
title = `✖ FAIL: ${pluralize(newResult.failed, 'failure', 'failures')}`;
}
NotificationService.notify(message, type, newResult.uuid);
setPageTitle(title);
fetchResults();
})
);
Expand Down
Loading

0 comments on commit ed17813

Please sign in to comment.