Skip to content

Allow enabling backtraces #364

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 9 commits into from
Jul 26, 2018
39 changes: 29 additions & 10 deletions tests/spec/features/backtrace_spec.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
require 'spec_helper'
require 'support/editor'
require 'support/playground_actions'

RSpec.feature "A backtrace is shown for certain errors", type: :feature, js: true do
before { pending "Backtracing has a large performance penalty" }
include PlaygroundActions

before do
visit '/'
editor.set(code)
within('.header') { click_on("Run") }
end

scenario "a stack trace is shown" do
within('.output-stderr') do
expect(page).to have_content 'stack backtrace:'
expect(page).to have_content 'rust_begin_unwind'
context "backtraces are enabled" do
before do
in_advanced_options_menu { choose 'enabled' }
within('.header') { click_on("Run") }
end

scenario "a backtrace is shown" do
within('.output-stderr') do
expect(page).to have_content 'stack backtrace:'
expect(page).to have_content 'rust_begin_unwind'
end
end

scenario "filenames link to that line of code" do
within('.output-stderr') do
expect(page).to have_link('main.rs:2')
expect(page).to have_link('main.rs:6')
end
end
end

scenario "filenames link to that line of code" do
within('.output-stderr') do
expect(page).to have_link('main.rs:2')
expect(page).to have_link('main.rs:6')
context "backtraces are disabled" do
before do
within('.header') { click_on("Run") }
end

scenario "the backtrace suggestion is a link" do
within('.output-stderr') do
expect(page).to have_link(text: /Run with .* a backtrace/)
end
end
end

Expand Down
21 changes: 18 additions & 3 deletions ui/frontend/AdvancedOptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React, { Fragment } from 'react';
import { connect } from 'react-redux';

import { changeEdition } from './actions';
import { changeBacktrace, changeEdition } from './actions';
import { changeNightlyEdition } from './actions';
import { Either as EitherConfig } from './ConfigElement';
import MenuGroup from './MenuGroup';
import { State } from './reducers';
import { getEditionSet, isEditionAvailable } from './selectors';
import { Edition } from './types';
import { getBacktraceSet, getEditionSet, isEditionAvailable } from './selectors';
import { Backtrace, Edition } from './types';

interface AdvancedOptionsMenuProps {
edition: Edition;
isEditionSet: boolean;
isEditionAvailable: boolean;
changeEdition: (_: Edition) => any;
backtrace: Backtrace;
isBacktraceSet: boolean;
changeBacktrace: (_: Backtrace) => any;
}

const AdvancedOptionsMenu: React.SFC<AdvancedOptionsMenuProps> = props => (
Expand All @@ -28,6 +31,15 @@ const AdvancedOptionsMenu: React.SFC<AdvancedOptionsMenuProps> = props => (
isNotDefault={props.isEditionSet}
onChange={props.changeEdition} />
{!props.isEditionAvailable && <EditionAside />}

<EitherConfig
id="backtrace"
name="Backtrace"
a={Backtrace.Disabled}
b={Backtrace.Enabled}
value={props.backtrace}
isNotDefault={props.isBacktraceSet}
onChange={props.changeBacktrace} />
</MenuGroup>
</Fragment>
);
Expand All @@ -43,10 +55,13 @@ const mapStateToProps = (state: State) => ({
isEditionSet: getEditionSet(state),
edition: state.configuration.edition,
isEditionAvailable: isEditionAvailable(state),
isBacktraceSet: getBacktraceSet(state),
backtrace: state.configuration.backtrace,
});

const mapDispatchToProps = ({
changeEdition: changeNightlyEdition,
changeBacktrace,
});

export default connect(mapStateToProps, mapDispatchToProps)(AdvancedOptionsMenu);
17 changes: 16 additions & 1 deletion ui/frontend/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getCrateType, isEditionAvailable, runAsTest } from './selectors';
import State from './state';
import {
AssemblyFlavor,
Backtrace,
Channel,
DemangleAssembly,
Edition,
Expand Down Expand Up @@ -51,6 +52,7 @@ export enum ActionType {
ChangeProcessAssembly = 'CHANGE_PROCESS_ASSEMBLY',
ChangeMode = 'CHANGE_MODE',
ChangeEdition = 'CHANGE_EDITION',
ChangeBacktrace = 'CHANGE_BACKTRACE',
ChangeFocus = 'CHANGE_FOCUS',
ExecuteRequest = 'EXECUTE_REQUEST',
ExecuteSucceeded = 'EXECUTE_SUCCEEDED',
Expand Down Expand Up @@ -131,6 +133,14 @@ export const changeNightlyEdition = (edition: Edition): ThunkAction => dispatch
dispatch(changeEdition(edition));
};

export const changeBacktrace = (backtrace: Backtrace) =>
createAction(ActionType.ChangeBacktrace, { backtrace });

export const reExecuteWithBacktrace = (): ThunkAction => dispatch => {
dispatch(changeBacktrace(Backtrace.Enabled));
dispatch(performExecute());
};

export const changeFocus = focus =>
createAction(ActionType.ChangeFocus, { focus });

Expand Down Expand Up @@ -184,6 +194,7 @@ interface ExecuteRequestBody {
tests: boolean;
code: string;
edition?: string;
backtrace: boolean;
}

export function performExecute(): ThunkAction {
Expand All @@ -195,8 +206,9 @@ export function performExecute(): ThunkAction {
const { code, configuration: { channel, mode, edition } } = state;
const crateType = getCrateType(state);
const tests = runAsTest(state);
const backtrace = state.configuration.backtrace === Backtrace.Enabled;

const body: ExecuteRequestBody = { channel, mode, crateType, tests, code };
const body: ExecuteRequestBody = { channel, mode, crateType, tests, code, backtrace };
if (isEditionAvailable(state)) {
body.edition = edition;
}
Expand Down Expand Up @@ -230,6 +242,7 @@ function performCompile(target, { request, success, failure }): ThunkAction {
} } = state;
const crateType = getCrateType(state);
const tests = runAsTest(state);
const backtrace = state.configuration.backtrace === Backtrace.Enabled;
const body: CompileRequestBody = {
channel,
mode,
Expand All @@ -240,6 +253,7 @@ function performCompile(target, { request, success, failure }): ThunkAction {
assemblyFlavor,
demangleAssembly,
processAssembly,
backtrace,
};
if (isEditionAvailable(state)) {
body.edition = edition;
Expand Down Expand Up @@ -569,6 +583,7 @@ export type Action =
| ReturnType<typeof changeKeybinding>
| ReturnType<typeof changeMode>
| ReturnType<typeof changeEdition>
| ReturnType<typeof changeBacktrace>
| ReturnType<typeof changeOrientation>
| ReturnType<typeof changeTheme>
| ReturnType<typeof requestExecute>
Expand Down
29 changes: 24 additions & 5 deletions ui/frontend/highlighting.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Prism from 'prismjs';

export function configureRustErrors({ gotoPosition, getChannel }) {
export function configureRustErrors({ gotoPosition, reExecuteWithBacktrace, getChannel }) {
Prism.languages.rust_errors = { // eslint-disable-line camelcase
'warning': /^warning:.*$/m,
'error': {
Expand All @@ -11,7 +11,13 @@ export function configureRustErrors({ gotoPosition, getChannel }) {
},
},
'error-location': /-->.*\n/,
'stack-trace-location': /at \/playground.*\n/,
'backtrace': {
pattern: /at src\/.*\n/,
inside: {
'backtrace-location': /src\/main.rs:(\d+)/,
},
},
'backtrace-enable': /Run with `RUST_BACKTRACE=1` for a backtrace/,
};

Prism.hooks.add('wrap', env => {
Expand All @@ -37,8 +43,13 @@ export function configureRustErrors({ gotoPosition, getChannel }) {
env.attributes['data-line'] = line;
env.attributes['data-col'] = col;
}
if (env.type === 'stack-trace-location') {
const errorMatch = /main.rs:(\d+)/.exec(env.content);
if (env.type === 'backtrace-enable') {
env.tag = 'a';
env.attributes.href = '#';
env.attributes['data-backtrace-enable'] = 'true';
}
if (env.type === 'backtrace-location') {
const errorMatch = /:(\d+)/.exec(env.content);
const [_, line] = errorMatch;
env.tag = 'a';
env.attributes.href = '#';
Expand All @@ -48,13 +59,21 @@ export function configureRustErrors({ gotoPosition, getChannel }) {
});

Prism.hooks.add('after-highlight', env => {
const links = env.element.querySelectorAll('.error-location, .stack-trace-location');
const links = env.element.querySelectorAll('.error-location, .backtrace-location');
Array.from(links).forEach((link: HTMLAnchorElement) => {
const { line, col } = link.dataset;
link.onclick = e => {
e.preventDefault();
gotoPosition(line, col);
};
});

const backtraceEnablers = env.element.querySelectorAll('.backtrace-enable');
Array.from(backtraceEnablers).forEach((link: HTMLAnchorElement) => {
link.onclick = e => {
e.preventDefault();
reExecuteWithBacktrace();
};
});
});
}
4 changes: 3 additions & 1 deletion ui/frontend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import persistState from 'redux-localstorage';
import thunk, { ThunkDispatch } from 'redux-thunk';
import * as url from 'url';

import { Action, gotoPosition, performCratesLoad, performVersionsLoad } from './actions';
import { Action, gotoPosition, performCratesLoad, performVersionsLoad, reExecuteWithBacktrace } from './actions';
import { configureRustErrors } from './highlighting';
import { deserialize, serialize } from './local_storage';
import PageSwitcher from './PageSwitcher';
import playgroundApp from './reducers';
import { State } from './reducers';
import Router from './Router';
import { Backtrace } from './types';

const baseUrl = url.resolve(window.location.href, '/');

Expand All @@ -31,6 +32,7 @@ const store = createStore(playgroundApp, initialState, enhancers);

configureRustErrors({
gotoPosition: (line, col) => store.dispatch(gotoPosition(line, col)),
reExecuteWithBacktrace: () => store.dispatch(reExecuteWithBacktrace()),
getChannel: () => store.getState().configuration.channel,
});

Expand Down
5 changes: 5 additions & 0 deletions ui/frontend/reducers/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Action, ActionType } from '../actions';
import {
AssemblyFlavor,
Backtrace,
Channel,
DemangleAssembly,
Edition,
Expand All @@ -22,6 +23,7 @@ export interface State {
channel: Channel;
mode: Mode;
edition: Edition;
backtrace: Backtrace;
}

export const DEFAULT: State = {
Expand All @@ -36,6 +38,7 @@ export const DEFAULT: State = {
channel: Channel.Stable,
mode: Mode.Debug,
edition: Edition.Rust2015,
backtrace: Backtrace.Disabled,
};

export default function configuration(state = DEFAULT, action: Action): State {
Expand Down Expand Up @@ -75,6 +78,8 @@ export default function configuration(state = DEFAULT, action: Action): State {
}
return { ...state, edition: action.edition };
}
case ActionType.ChangeBacktrace:
return { ...state, backtrace: action.backtrace };
default:
return state;
}
Expand Down
17 changes: 12 additions & 5 deletions ui/frontend/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
import * as url from 'url';

import { State } from '../reducers';
import { Channel, Edition } from '../types';
import { Backtrace, Channel, Edition } from '../types';

const getCode = state => state.code;

Expand Down Expand Up @@ -53,14 +53,21 @@ export const getChannelLabel = (state: State) => {
return `${channel}`;
};

export const getAdvancedOptionsSet = (state: State) => (
getEditionSet(state)
);

export const getEditionSet = (state: State) => (
state.configuration.edition !== Edition.Rust2015
);

export const getBacktraceSet = (state: State) => (
state.configuration.backtrace !== Backtrace.Disabled
);

export const getAdvancedOptionsSet = createSelector(
getEditionSet, getBacktraceSet,
(editionSet, backtraceSet) => (
editionSet || backtraceSet
),
);

const baseUrlSelector = (state: State) =>
state.globalConfiguration.baseUrl;

Expand Down
5 changes: 5 additions & 0 deletions ui/frontend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ export enum Edition {
Rust2015 = '2015',
Rust2018 = '2018',
}

export enum Backtrace {
Disabled = 'disabled',
Enabled = 'enabled',
}
5 changes: 5 additions & 0 deletions ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ struct CompileRequest {
#[serde(rename = "crateType")]
crate_type: String,
tests: bool,
backtrace: bool,
code: String,
}

Expand All @@ -545,6 +546,7 @@ struct ExecuteRequest {
#[serde(rename = "crateType")]
crate_type: String,
tests: bool,
backtrace: bool,
code: String,
}

Expand Down Expand Up @@ -657,6 +659,7 @@ impl TryFrom<CompileRequest> for sandbox::CompileRequest {
edition: parse_edition(&me.edition)?,
crate_type: parse_crate_type(&me.crate_type)?,
tests: me.tests,
backtrace: me.backtrace,
code: me.code,
})
}
Expand All @@ -683,6 +686,7 @@ impl TryFrom<ExecuteRequest> for sandbox::ExecuteRequest {
edition: parse_edition(&me.edition)?,
crate_type: try!(parse_crate_type(&me.crate_type)),
tests: me.tests,
backtrace: me.backtrace,
code: me.code,
})
}
Expand Down Expand Up @@ -779,6 +783,7 @@ impl TryFrom<EvaluateRequest> for sandbox::ExecuteRequest {
edition: None, // FIXME: What should this be?
crate_type: sandbox::CrateType::Binary,
tests: false,
backtrace: false,
code: me.code,
})
}
Expand Down
Loading