Skip to content

Commit 1331a6f

Browse files
authored
Merge pull request #364 from integer32llc/backtrace
Allow enabling backtraces
2 parents 1ffb961 + 0d18b6a commit 1331a6f

File tree

10 files changed

+275
-106
lines changed

10 files changed

+275
-106
lines changed

tests/spec/features/backtrace_spec.rb

+29-10
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
require 'spec_helper'
22
require 'support/editor'
3+
require 'support/playground_actions'
34

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

78
before do
89
visit '/'
910
editor.set(code)
10-
within('.header') { click_on("Run") }
1111
end
1212

13-
scenario "a stack trace is shown" do
14-
within('.output-stderr') do
15-
expect(page).to have_content 'stack backtrace:'
16-
expect(page).to have_content 'rust_begin_unwind'
13+
context "backtraces are enabled" do
14+
before do
15+
in_advanced_options_menu { choose 'enabled' }
16+
within('.header') { click_on("Run") }
17+
end
18+
19+
scenario "a backtrace is shown" do
20+
within('.output-stderr') do
21+
expect(page).to have_content 'stack backtrace:'
22+
expect(page).to have_content 'rust_begin_unwind'
23+
end
24+
end
25+
26+
scenario "filenames link to that line of code" do
27+
within('.output-stderr') do
28+
expect(page).to have_link('main.rs:2')
29+
expect(page).to have_link('main.rs:6')
30+
end
1731
end
1832
end
1933

20-
scenario "filenames link to that line of code" do
21-
within('.output-stderr') do
22-
expect(page).to have_link('main.rs:2')
23-
expect(page).to have_link('main.rs:6')
34+
context "backtraces are disabled" do
35+
before do
36+
within('.header') { click_on("Run") }
37+
end
38+
39+
scenario "the backtrace suggestion is a link" do
40+
within('.output-stderr') do
41+
expect(page).to have_link(text: /Run with .* a backtrace/)
42+
end
2443
end
2544
end
2645

ui/frontend/AdvancedOptionsMenu.tsx

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import React, { Fragment } from 'react';
22
import { connect } from 'react-redux';
33

4-
import { changeEdition } from './actions';
4+
import { changeBacktrace, changeEdition } from './actions';
55
import { changeNightlyEdition } from './actions';
66
import { Either as EitherConfig } from './ConfigElement';
77
import MenuGroup from './MenuGroup';
88
import { State } from './reducers';
9-
import { getEditionSet, isEditionAvailable } from './selectors';
10-
import { Edition } from './types';
9+
import { getBacktraceSet, getEditionSet, isEditionAvailable } from './selectors';
10+
import { Backtrace, Edition } from './types';
1111

1212
interface AdvancedOptionsMenuProps {
1313
edition: Edition;
1414
isEditionSet: boolean;
1515
isEditionAvailable: boolean;
1616
changeEdition: (_: Edition) => any;
17+
backtrace: Backtrace;
18+
isBacktraceSet: boolean;
19+
changeBacktrace: (_: Backtrace) => any;
1720
}
1821

1922
const AdvancedOptionsMenu: React.SFC<AdvancedOptionsMenuProps> = props => (
@@ -28,6 +31,15 @@ const AdvancedOptionsMenu: React.SFC<AdvancedOptionsMenuProps> = props => (
2831
isNotDefault={props.isEditionSet}
2932
onChange={props.changeEdition} />
3033
{!props.isEditionAvailable && <EditionAside />}
34+
35+
<EitherConfig
36+
id="backtrace"
37+
name="Backtrace"
38+
a={Backtrace.Disabled}
39+
b={Backtrace.Enabled}
40+
value={props.backtrace}
41+
isNotDefault={props.isBacktraceSet}
42+
onChange={props.changeBacktrace} />
3143
</MenuGroup>
3244
</Fragment>
3345
);
@@ -43,10 +55,13 @@ const mapStateToProps = (state: State) => ({
4355
isEditionSet: getEditionSet(state),
4456
edition: state.configuration.edition,
4557
isEditionAvailable: isEditionAvailable(state),
58+
isBacktraceSet: getBacktraceSet(state),
59+
backtrace: state.configuration.backtrace,
4660
});
4761

4862
const mapDispatchToProps = ({
4963
changeEdition: changeNightlyEdition,
64+
changeBacktrace,
5065
});
5166

5267
export default connect(mapStateToProps, mapDispatchToProps)(AdvancedOptionsMenu);

ui/frontend/actions.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getCrateType, isEditionAvailable, runAsTest } from './selectors';
66
import State from './state';
77
import {
88
AssemblyFlavor,
9+
Backtrace,
910
Channel,
1011
DemangleAssembly,
1112
Edition,
@@ -51,6 +52,7 @@ export enum ActionType {
5152
ChangeProcessAssembly = 'CHANGE_PROCESS_ASSEMBLY',
5253
ChangeMode = 'CHANGE_MODE',
5354
ChangeEdition = 'CHANGE_EDITION',
55+
ChangeBacktrace = 'CHANGE_BACKTRACE',
5456
ChangeFocus = 'CHANGE_FOCUS',
5557
ExecuteRequest = 'EXECUTE_REQUEST',
5658
ExecuteSucceeded = 'EXECUTE_SUCCEEDED',
@@ -131,6 +133,14 @@ export const changeNightlyEdition = (edition: Edition): ThunkAction => dispatch
131133
dispatch(changeEdition(edition));
132134
};
133135

136+
export const changeBacktrace = (backtrace: Backtrace) =>
137+
createAction(ActionType.ChangeBacktrace, { backtrace });
138+
139+
export const reExecuteWithBacktrace = (): ThunkAction => dispatch => {
140+
dispatch(changeBacktrace(Backtrace.Enabled));
141+
dispatch(performExecute());
142+
};
143+
134144
export const changeFocus = focus =>
135145
createAction(ActionType.ChangeFocus, { focus });
136146

@@ -184,6 +194,7 @@ interface ExecuteRequestBody {
184194
tests: boolean;
185195
code: string;
186196
edition?: string;
197+
backtrace: boolean;
187198
}
188199

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

199-
const body: ExecuteRequestBody = { channel, mode, crateType, tests, code };
211+
const body: ExecuteRequestBody = { channel, mode, crateType, tests, code, backtrace };
200212
if (isEditionAvailable(state)) {
201213
body.edition = edition;
202214
}
@@ -230,6 +242,7 @@ function performCompile(target, { request, success, failure }): ThunkAction {
230242
} } = state;
231243
const crateType = getCrateType(state);
232244
const tests = runAsTest(state);
245+
const backtrace = state.configuration.backtrace === Backtrace.Enabled;
233246
const body: CompileRequestBody = {
234247
channel,
235248
mode,
@@ -240,6 +253,7 @@ function performCompile(target, { request, success, failure }): ThunkAction {
240253
assemblyFlavor,
241254
demangleAssembly,
242255
processAssembly,
256+
backtrace,
243257
};
244258
if (isEditionAvailable(state)) {
245259
body.edition = edition;
@@ -569,6 +583,7 @@ export type Action =
569583
| ReturnType<typeof changeKeybinding>
570584
| ReturnType<typeof changeMode>
571585
| ReturnType<typeof changeEdition>
586+
| ReturnType<typeof changeBacktrace>
572587
| ReturnType<typeof changeOrientation>
573588
| ReturnType<typeof changeTheme>
574589
| ReturnType<typeof requestExecute>

ui/frontend/highlighting.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Prism from 'prismjs';
22

3-
export function configureRustErrors({ gotoPosition, getChannel }) {
3+
export function configureRustErrors({ gotoPosition, reExecuteWithBacktrace, getChannel }) {
44
Prism.languages.rust_errors = { // eslint-disable-line camelcase
55
'warning': /^warning:.*$/m,
66
'error': {
@@ -11,7 +11,13 @@ export function configureRustErrors({ gotoPosition, getChannel }) {
1111
},
1212
},
1313
'error-location': /-->.*\n/,
14-
'stack-trace-location': /at \/playground.*\n/,
14+
'backtrace': {
15+
pattern: /at src\/.*\n/,
16+
inside: {
17+
'backtrace-location': /src\/main.rs:(\d+)/,
18+
},
19+
},
20+
'backtrace-enable': /Run with `RUST_BACKTRACE=1` for a backtrace/,
1521
};
1622

1723
Prism.hooks.add('wrap', env => {
@@ -37,8 +43,13 @@ export function configureRustErrors({ gotoPosition, getChannel }) {
3743
env.attributes['data-line'] = line;
3844
env.attributes['data-col'] = col;
3945
}
40-
if (env.type === 'stack-trace-location') {
41-
const errorMatch = /main.rs:(\d+)/.exec(env.content);
46+
if (env.type === 'backtrace-enable') {
47+
env.tag = 'a';
48+
env.attributes.href = '#';
49+
env.attributes['data-backtrace-enable'] = 'true';
50+
}
51+
if (env.type === 'backtrace-location') {
52+
const errorMatch = /:(\d+)/.exec(env.content);
4253
const [_, line] = errorMatch;
4354
env.tag = 'a';
4455
env.attributes.href = '#';
@@ -48,13 +59,21 @@ export function configureRustErrors({ gotoPosition, getChannel }) {
4859
});
4960

5061
Prism.hooks.add('after-highlight', env => {
51-
const links = env.element.querySelectorAll('.error-location, .stack-trace-location');
62+
const links = env.element.querySelectorAll('.error-location, .backtrace-location');
5263
Array.from(links).forEach((link: HTMLAnchorElement) => {
5364
const { line, col } = link.dataset;
5465
link.onclick = e => {
5566
e.preventDefault();
5667
gotoPosition(line, col);
5768
};
5869
});
70+
71+
const backtraceEnablers = env.element.querySelectorAll('.backtrace-enable');
72+
Array.from(backtraceEnablers).forEach((link: HTMLAnchorElement) => {
73+
link.onclick = e => {
74+
e.preventDefault();
75+
reExecuteWithBacktrace();
76+
};
77+
});
5978
});
6079
}

ui/frontend/index.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import persistState from 'redux-localstorage';
88
import thunk, { ThunkDispatch } from 'redux-thunk';
99
import * as url from 'url';
1010

11-
import { Action, gotoPosition, performCratesLoad, performVersionsLoad } from './actions';
11+
import { Action, gotoPosition, performCratesLoad, performVersionsLoad, reExecuteWithBacktrace } from './actions';
1212
import { configureRustErrors } from './highlighting';
1313
import { deserialize, serialize } from './local_storage';
1414
import PageSwitcher from './PageSwitcher';
1515
import playgroundApp from './reducers';
1616
import { State } from './reducers';
1717
import Router from './Router';
18+
import { Backtrace } from './types';
1819

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

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

3233
configureRustErrors({
3334
gotoPosition: (line, col) => store.dispatch(gotoPosition(line, col)),
35+
reExecuteWithBacktrace: () => store.dispatch(reExecuteWithBacktrace()),
3436
getChannel: () => store.getState().configuration.channel,
3537
});
3638

ui/frontend/reducers/configuration.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Action, ActionType } from '../actions';
22
import {
33
AssemblyFlavor,
4+
Backtrace,
45
Channel,
56
DemangleAssembly,
67
Edition,
@@ -22,6 +23,7 @@ export interface State {
2223
channel: Channel;
2324
mode: Mode;
2425
edition: Edition;
26+
backtrace: Backtrace;
2527
}
2628

2729
export const DEFAULT: State = {
@@ -36,6 +38,7 @@ export const DEFAULT: State = {
3638
channel: Channel.Stable,
3739
mode: Mode.Debug,
3840
edition: Edition.Rust2015,
41+
backtrace: Backtrace.Disabled,
3942
};
4043

4144
export default function configuration(state = DEFAULT, action: Action): State {
@@ -75,6 +78,8 @@ export default function configuration(state = DEFAULT, action: Action): State {
7578
}
7679
return { ...state, edition: action.edition };
7780
}
81+
case ActionType.ChangeBacktrace:
82+
return { ...state, backtrace: action.backtrace };
7883
default:
7984
return state;
8085
}

ui/frontend/selectors/index.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
33
import * as url from 'url';
44

55
import { State } from '../reducers';
6-
import { Channel, Edition } from '../types';
6+
import { Backtrace, Channel, Edition } from '../types';
77

88
const getCode = state => state.code;
99

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

56-
export const getAdvancedOptionsSet = (state: State) => (
57-
getEditionSet(state)
58-
);
59-
6056
export const getEditionSet = (state: State) => (
6157
state.configuration.edition !== Edition.Rust2015
6258
);
6359

60+
export const getBacktraceSet = (state: State) => (
61+
state.configuration.backtrace !== Backtrace.Disabled
62+
);
63+
64+
export const getAdvancedOptionsSet = createSelector(
65+
getEditionSet, getBacktraceSet,
66+
(editionSet, backtraceSet) => (
67+
editionSet || backtraceSet
68+
),
69+
);
70+
6471
const baseUrlSelector = (state: State) =>
6572
state.globalConfiguration.baseUrl;
6673

ui/frontend/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,8 @@ export enum Edition {
6666
Rust2015 = '2015',
6767
Rust2018 = '2018',
6868
}
69+
70+
export enum Backtrace {
71+
Disabled = 'disabled',
72+
Enabled = 'enabled',
73+
}

ui/src/main.rs

+5
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ struct CompileRequest {
525525
#[serde(rename = "crateType")]
526526
crate_type: String,
527527
tests: bool,
528+
backtrace: bool,
528529
code: String,
529530
}
530531

@@ -545,6 +546,7 @@ struct ExecuteRequest {
545546
#[serde(rename = "crateType")]
546547
crate_type: String,
547548
tests: bool,
549+
backtrace: bool,
548550
code: String,
549551
}
550552

@@ -657,6 +659,7 @@ impl TryFrom<CompileRequest> for sandbox::CompileRequest {
657659
edition: parse_edition(&me.edition)?,
658660
crate_type: parse_crate_type(&me.crate_type)?,
659661
tests: me.tests,
662+
backtrace: me.backtrace,
660663
code: me.code,
661664
})
662665
}
@@ -683,6 +686,7 @@ impl TryFrom<ExecuteRequest> for sandbox::ExecuteRequest {
683686
edition: parse_edition(&me.edition)?,
684687
crate_type: try!(parse_crate_type(&me.crate_type)),
685688
tests: me.tests,
689+
backtrace: me.backtrace,
686690
code: me.code,
687691
})
688692
}
@@ -779,6 +783,7 @@ impl TryFrom<EvaluateRequest> for sandbox::ExecuteRequest {
779783
edition: None, // FIXME: What should this be?
780784
crate_type: sandbox::CrateType::Binary,
781785
tests: false,
786+
backtrace: false,
782787
code: me.code,
783788
})
784789
}

0 commit comments

Comments
 (0)