Skip to content

Feature/test failure #311

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

Merged
merged 4 commits into from
May 4, 2020
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,9 @@ Resulting in a folder structure like the following:
- Continue an incomplete tutorial started in the same workspace. Choose the "continue" path from the start screen. Progress is stored in local storage in the workspace.

![continue tutorial](./docs/images/continue-tutorial.png)

## [0.5.0]

- Show error messages in the webview UI

![fail message in webview](./docs/images/fail-message-in-webview.png)
Binary file added docs/images/fail-message-in-webview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/channel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
import { readFile } from 'fs'
import { join } from 'path'
import { promisify } from 'util'
import { showOutput } from '../services/testRunner/output'
import { WORKSPACE_ROOT } from '../environment'

const readFileAsync = promisify(readFile)
Expand Down Expand Up @@ -300,7 +301,9 @@ class Channel implements Channel {
// update progress when a level is deemed complete in the client
await this.context.progress.syncProgress(action.payload.progress)
return

case 'EDITOR_OPEN_LOGS':
const channel = action.payload.channel
await showOutput(channel)
default:
logger(`No match for action type: ${actionType}`)
return
Expand Down
6 changes: 3 additions & 3 deletions src/editor/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as TT from 'typings/tutorial'
import * as vscode from 'vscode'
import createTestRunner from '../services/testRunner'
import { setupActions } from '../actions/setupActions'
import createWebView from '../webview'
import createWebView from '../services/webview'
import logger from '../services/logger'

export const COMMANDS = {
Expand Down Expand Up @@ -62,9 +62,9 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
// send test pass message back to client
webview.send({ type: 'TEST_PASS', payload: { position } })
},
onFail: (position: T.Position, message: string) => {
onFail: (position: T.Position, failSummary: T.TestFail): void => {
// send test fail message back to client with failure message
webview.send({ type: 'TEST_FAIL', payload: { position, message } })
webview.send({ type: 'TEST_FAIL', payload: { position, fail: failSummary } })
},
onError: (position: T.Position) => {
// TODO: send test error message back to client
Expand Down
2 changes: 1 addition & 1 deletion src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type Env = 'test' | 'local' | 'development' | 'production'
export const NODE_ENV: Env = process.env.NODE_ENV || 'production'

// toggle logging in development
export const LOG = false
export const LOG = true

// error logging tool
export const SENTRY_DSN: string | null = null
Expand Down
2 changes: 1 addition & 1 deletion src/services/testRunner/formatOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ParserOutput, Fail } from './parser'
// export const formatSuccessOutput = (tap: ParserOutput): string => {}

export const formatFailOutput = (tap: ParserOutput): string => {
let output = `FAILED TESTS\n`
let output = `FAILED TEST LOG\n`
tap.failed.forEach((fail: Fail) => {
const details = fail.details ? `\n${fail.details}\n` : ''
const logs = fail.logs ? `\n${fail.logs.join('\n')}\n` : ''
Expand Down
18 changes: 11 additions & 7 deletions src/services/testRunner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import logger from '../logger'
import parser from './parser'
import { debounce, throttle } from './throttle'
import onError from '../sentry/onError'
import { clearOutput, displayOutput } from './output'
import { clearOutput, addOutput } from './output'
import { formatFailOutput } from './formatOutput'

interface Callbacks {
onSuccess(position: T.Position): void
onFail(position: T.Position, message: string): void
onFail(position: T.Position, failSummary: T.TestFail): void
onRun(position: T.Position): void
onError(position: T.Position): void
}
Expand Down Expand Up @@ -51,20 +51,24 @@ const createTestRunner = (config: TT.TutorialTestRunnerConfig, callbacks: Callba

const tap = parser(stdout || '')

displayOutput({ channel: logChannelName, text: tap.logs.join('\n'), show: false })
addOutput({ channel: logChannelName, text: tap.logs.join('\n'), show: false })

if (stderr) {
// FAIL also trigger stderr
if (stdout && stdout.length && !tap.ok) {
const firstFailMessage = tap.failed[0].message
callbacks.onFail(position, firstFailMessage)
const firstFail = tap.failed[0]
const failSummary = {
title: firstFail.message || 'Test Failed',
description: firstFail.details || 'Unknown error',
}
callbacks.onFail(position, failSummary)
const output = formatFailOutput(tap)
displayOutput({ channel: failChannelName, text: output, show: true })
addOutput({ channel: failChannelName, text: output, show: true })
return
} else {
callbacks.onError(position)
// open terminal with error string
displayOutput({ channel: failChannelName, text: stderr, show: true })
addOutput({ channel: failChannelName, text: stderr, show: true })
return
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/services/testRunner/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,25 @@ const getOutputChannel = (name: string): vscode.OutputChannel => {
return channels[name]
}

interface DisplayOutput {
interface ChannelOutput {
channel: string
text: string
show?: boolean
}

export const displayOutput = (params: DisplayOutput) => {
export const addOutput = (params: ChannelOutput) => {
const channel = getOutputChannel(params.channel)
channel.clear()
channel.show(params.show || false)
channel.append(params.text)
}

export const showOutput = (channelName: string) => {
const channel = getOutputChannel(channelName)
channel.show()
}

export const clearOutput = (channelName: string) => {
const channel = getOutputChannel(channelName)
channel.show(false)
channel.clear()
channel.hide()
}
2 changes: 1 addition & 1 deletion src/webview/index.ts → src/services/webview/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as path from 'path'
import { Action } from 'typings'
import * as vscode from 'vscode'
import Channel from '../channel'
import Channel from '../../channel'
import render from './render'

interface ReactWebViewProps {
Expand Down
2 changes: 1 addition & 1 deletion src/webview/render.ts → src/services/webview/render.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { JSDOM } from 'jsdom'
import * as path from 'path'
import * as vscode from 'vscode'
import onError from '../services/sentry/onError'
import onError from '../sentry/onError'

const getNonce = (): string => {
let text = ''
Expand Down
6 changes: 6 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface TestStatus {
type: 'success' | 'warning' | 'error' | 'loading'
title: string
content?: string
timeout?: number
}

export interface MachineContext {
Expand Down Expand Up @@ -116,3 +117,8 @@ export interface ProcessEvent {
description: string
status: 'RUNNING' | 'SUCCESS' | 'FAIL' | 'ERROR'
}

export type TestFail = {
title: string
description: string
}
6 changes: 5 additions & 1 deletion web-app/src/components/Message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Props {
closeable?: boolean
onClose?: () => void
handleClose?: () => void
children?: React.ReactElement | null
}

const Message = (props: Props) => {
Expand All @@ -30,7 +31,10 @@ const Message = (props: Props) => {
onClose={onClose}
shape={props.shape}
>
{props.content}
<div>
<div>{props.content}</div>
<div>{props.children}</div>
</div>
</AlifdMessage>
)
}
Expand Down
8 changes: 5 additions & 3 deletions web-app/src/components/ProcessMessages/TestMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { css, jsx } from '@emotion/core'

const durations = {
success: 1000,
warning: 4500,
warning: 20000,
error: 4500,
loading: 300000,
}
Expand All @@ -24,7 +24,7 @@ const useTimeout = ({ duration, key }: { duration: number; key: string }) => {
return timeoutClose
}

const TestMessage = (props: T.TestStatus) => {
const TestMessage = (props: T.TestStatus & { children?: React.ReactElement | null }) => {
const duration = durations[props.type]
const timeoutClose = useTimeout({ duration, key: props.title })
return (
Expand All @@ -36,7 +36,9 @@ const TestMessage = (props: T.TestStatus) => {
size="medium"
closeable={props.type !== 'loading'}
content={props.content}
/>
>
{props.children}
</Message>
)
}

Expand Down
18 changes: 16 additions & 2 deletions web-app/src/components/ProcessMessages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Message from '../Message'
import * as React from 'react'
import * as T from 'typings'
import Button from '../Button'
import { css, jsx } from '@emotion/core'
import TestMessage from './TestMessage'

interface Props {
testStatus?: T.TestStatus | null
processes: T.ProcessEvent[]
onOpenLogs?: (channel: string) => void
}

const styles = {
Expand All @@ -17,9 +19,21 @@ const styles = {
}

// display a list of active processes
const ProcessMessages = ({ processes, testStatus }: Props) => {
const ProcessMessages = ({ processes, testStatus, onOpenLogs }: Props) => {
if (testStatus) {
return <TestMessage {...testStatus} />
return (
<TestMessage {...testStatus}>
{testStatus.type === 'warning' ? (
<Button
onClick={() => onOpenLogs && onOpenLogs('CodeRoad (Tests)')}
type="normal"
style={{ marginTop: '0.8rem' }}
>
Open Logs
</Button>
) : null}
</TestMessage>
)
}
if (!processes.length) {
return null
Expand Down
4 changes: 3 additions & 1 deletion web-app/src/containers/Tutorial/components/Level.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ interface Props {
testStatus: T.TestStatus | null
onContinue(): void
onLoadSolution(): void
onOpenLogs(channel: string): void
}

const Level = ({
Expand All @@ -107,6 +108,7 @@ const Level = ({
status,
onContinue,
onLoadSolution,
onOpenLogs,
processes,
testStatus,
}: Props) => {
Expand Down Expand Up @@ -170,7 +172,7 @@ const Level = ({

{(testStatus || processes.length > 0) && (
<div css={styles.processes}>
<ProcessMessages processes={processes} testStatus={testStatus} />
<ProcessMessages processes={processes} testStatus={testStatus} onOpenLogs={onOpenLogs} />
</div>
)}

Expand Down
5 changes: 5 additions & 0 deletions web-app/src/containers/Tutorial/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const TutorialPage = (props: PageProps) => {
props.send({ type: 'STEP_SOLUTION_LOAD' })
}

const onOpenLogs = (channel: string): void => {
props.send({ type: 'OPEN_LOGS', payload: { channel } })
}

const steps = levelData.steps.map((step: TT.Step) => {
// label step status for step component
let status: T.ProgressStatus = 'INCOMPLETE'
Expand Down Expand Up @@ -61,6 +65,7 @@ const TutorialPage = (props: PageProps) => {
status={progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE'}
onContinue={onContinue}
onLoadSolution={onLoadSolution}
onOpenLogs={onOpenLogs}
processes={processes}
testStatus={testStatus}
/>
Expand Down
20 changes: 13 additions & 7 deletions web-app/src/services/state/actions/editor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as CR from 'typings'
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import * as selectors from '../../selectors'

Expand All @@ -8,7 +8,7 @@ export default (editorSend: any) => ({
type: 'EDITOR_STARTUP',
})
},
configureNewTutorial(context: CR.MachineContext) {
configureNewTutorial(context: T.MachineContext) {
editorSend({
type: 'EDITOR_TUTORIAL_CONFIG',
payload: {
Expand All @@ -17,7 +17,7 @@ export default (editorSend: any) => ({
},
})
},
continueConfig(context: CR.MachineContext) {
continueConfig(context: T.MachineContext) {
editorSend({
type: 'EDITOR_TUTORIAL_CONTINUE_CONFIG',
payload: {
Expand All @@ -26,7 +26,7 @@ export default (editorSend: any) => ({
},
})
},
loadLevel(context: CR.MachineContext): void {
loadLevel(context: T.MachineContext): void {
const level: TT.Level = selectors.currentLevel(context)
const step: TT.Step | null = selectors.currentStep(context)
// load step actions
Expand All @@ -41,7 +41,7 @@ export default (editorSend: any) => ({
},
})
},
loadStep(context: CR.MachineContext): void {
loadStep(context: T.MachineContext): void {
const step: TT.Step | null = selectors.currentStep(context)
if (step && step.setup) {
// load step actions
Expand All @@ -58,7 +58,7 @@ export default (editorSend: any) => ({
})
}
},
editorLoadSolution(context: CR.MachineContext): void {
editorLoadSolution(context: T.MachineContext): void {
const step: TT.Step | null = selectors.currentStep(context)
// tell editor to load solution commit
if (step && step.solution) {
Expand All @@ -74,7 +74,7 @@ export default (editorSend: any) => ({
})
}
},
syncLevelProgress(context: CR.MachineContext): void {
syncLevelProgress(context: T.MachineContext): void {
editorSend({
type: 'EDITOR_SYNC_PROGRESS',
payload: {
Expand All @@ -95,4 +95,10 @@ export default (editorSend: any) => ({
type: 'EDITOR_REQUEST_WORKSPACE',
})
},
editorOpenLogs(context: T.MachineContext, event: T.MachineEvent): void {
editorSend({
type: 'EDITOR_OPEN_LOGS',
payload: { channel: event.payload.channel },
})
},
})
4 changes: 2 additions & 2 deletions web-app/src/services/state/actions/testNotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const testActions: ActionFunctionMap<CR.MachineContext, CR.MachineEvent> = {
testFail: assign({
testStatus: (context, event) => ({
type: 'warning',
title: 'Fail!',
content: event.payload.message,
title: event.payload.fail.title,
content: event.payload.fail.description,
}),
}),
// @ts-ignore
Expand Down
Loading