Skip to content

Commit 29ea303

Browse files
committed
Auto merge of #363 - pietroalbini:spurious-aware, r=pietroalbini
Detect and categorize spurious failures This PR automatically detects and reports OOMs and timeouts: ![screenshot from 2018-11-10 10-58-03](https://user-images.githubusercontent.com/2299951/48300828-fee61200-e4e3-11e8-9bab-697878b6989d.png) It also categorizes them as spurious when they are regressions/fixes: ![screenshot from 2018-11-10 12-23-23](https://user-images.githubusercontent.com/2299951/48300827-fee61200-e4e3-11e8-851f-70fa95f07569.png)
2 parents 920b007 + 00808b0 commit 29ea303

File tree

18 files changed

+508
-195
lines changed

18 files changed

+508
-195
lines changed

Cargo.lock

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ lazy_static = "1.0"
2727
mime = "0.3.1"
2828
minifier = { version = "0.0.20", features = ["html"] }
2929
nix = "0.11.0"
30+
paste = "0.1.3"
3031
petgraph = "0.4.11"
3132
r2d2 = "0.8.2"
3233
r2d2_sqlite = "0.7.0"

src/docker.rs

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::fs;
66
use std::path::{Path, PathBuf};
77
use utils::size::Size;
88

9-
pub static IMAGE_NAME: &'static str = "crater";
9+
pub(crate) static IMAGE_NAME: &'static str = "crater";
1010

1111
/// Builds the docker container image, 'crater', what will be used
1212
/// to isolate builds from each other. This expects the Dockerfile
@@ -24,7 +24,7 @@ pub(crate) fn is_running() -> bool {
2424
}
2525

2626
#[derive(Copy, Clone)]
27-
pub enum MountPerms {
27+
pub(crate) enum MountPerms {
2828
ReadWrite,
2929
ReadOnly,
3030
}
@@ -50,7 +50,7 @@ impl MountConfig {
5050
}
5151
}
5252

53-
pub struct ContainerBuilder {
53+
pub(crate) struct ContainerBuilder {
5454
image: String,
5555
mounts: Vec<MountConfig>,
5656
env: Vec<(String, String)>,
@@ -59,7 +59,7 @@ pub struct ContainerBuilder {
5959
}
6060

6161
impl ContainerBuilder {
62-
pub fn new<S: Into<String>>(image: S) -> Self {
62+
pub(crate) fn new<S: Into<String>>(image: S) -> Self {
6363
ContainerBuilder {
6464
image: image.into(),
6565
mounts: Vec::new(),
@@ -69,7 +69,7 @@ impl ContainerBuilder {
6969
}
7070
}
7171

72-
pub fn mount<P1: Into<PathBuf>, P2: Into<PathBuf>>(
72+
pub(crate) fn mount<P1: Into<PathBuf>, P2: Into<PathBuf>>(
7373
mut self,
7474
host_path: P1,
7575
container_path: P2,
@@ -83,22 +83,22 @@ impl ContainerBuilder {
8383
self
8484
}
8585

86-
pub fn env<S1: Into<String>, S2: Into<String>>(mut self, key: S1, value: S2) -> Self {
86+
pub(crate) fn env<S1: Into<String>, S2: Into<String>>(mut self, key: S1, value: S2) -> Self {
8787
self.env.push((key.into(), value.into()));
8888
self
8989
}
9090

91-
pub fn memory_limit(mut self, limit: Option<Size>) -> Self {
91+
pub(crate) fn memory_limit(mut self, limit: Option<Size>) -> Self {
9292
self.memory_limit = limit;
9393
self
9494
}
9595

96-
pub fn enable_networking(mut self, enable: bool) -> Self {
96+
pub(crate) fn enable_networking(mut self, enable: bool) -> Self {
9797
self.enable_networking = enable;
9898
self
9999
}
100100

101-
pub fn create(self) -> Fallible<Container> {
101+
pub(crate) fn create(self) -> Fallible<Container> {
102102
let mut args: Vec<String> = vec!["create".into()];
103103

104104
for mount in &self.mounts {
@@ -128,7 +128,7 @@ impl ContainerBuilder {
128128
Ok(Container { id: out[0].clone() })
129129
}
130130

131-
pub fn run(self, quiet: bool) -> Fallible<()> {
131+
pub(crate) fn run(self, quiet: bool) -> Fallible<()> {
132132
let container = self.create()?;
133133

134134
// Ensure the container is properly deleted even if something panics
@@ -152,8 +152,26 @@ fn absolute(path: &Path) -> PathBuf {
152152
}
153153
}
154154

155+
#[derive(Debug, Fail)]
156+
pub(crate) enum DockerError {
157+
#[fail(display = "container ran out of memory")]
158+
ContainerOOM,
159+
}
160+
161+
#[derive(Deserialize)]
162+
#[serde(rename_all = "PascalCase")]
163+
struct InspectContainer {
164+
state: InspectState,
165+
}
166+
167+
#[derive(Deserialize)]
168+
struct InspectState {
169+
#[serde(rename = "OOMKilled")]
170+
oom_killed: bool,
171+
}
172+
155173
#[derive(Serialize, Deserialize, Clone, Debug)]
156-
pub struct Container {
174+
pub(crate) struct Container {
157175
// Docker container ID
158176
id: String,
159177
}
@@ -165,14 +183,37 @@ impl Display for Container {
165183
}
166184

167185
impl Container {
168-
pub fn run(&self, quiet: bool) -> Fallible<()> {
169-
RunCommand::new("docker")
186+
fn inspect(&self) -> Fallible<InspectContainer> {
187+
let output = RunCommand::new("docker")
188+
.args(&["inspect", &self.id])
189+
.hide_output(true)
190+
.run_capture()?;
191+
192+
let mut data: Vec<InspectContainer> = ::serde_json::from_str(&output.0.join("\n"))?;
193+
assert_eq!(data.len(), 1);
194+
Ok(data.pop().unwrap())
195+
}
196+
197+
pub(crate) fn run(&self, quiet: bool) -> Fallible<()> {
198+
let res = RunCommand::new("docker")
170199
.args(&["start", "-a", &self.id])
171200
.quiet(quiet)
172-
.run()
201+
.run();
202+
let details = self.inspect()?;
203+
204+
// Return a different error if the container was killed due to an OOM
205+
if details.state.oom_killed {
206+
if let Err(err) = res {
207+
Err(err.context(DockerError::ContainerOOM).into())
208+
} else {
209+
Err(DockerError::ContainerOOM.into())
210+
}
211+
} else {
212+
res
213+
}
173214
}
174215

175-
pub fn delete(&self) -> Fallible<()> {
216+
pub(crate) fn delete(&self) -> Fallible<()> {
176217
RunCommand::new("docker")
177218
.args(&["rm", "-f", &self.id])
178219
.run()

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ extern crate mime;
2121
extern crate minifier;
2222
#[cfg(unix)]
2323
extern crate nix;
24+
#[macro_use]
25+
extern crate paste;
2426
extern crate petgraph;
2527
extern crate r2d2;
2628
extern crate r2d2_sqlite;

src/prelude.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
1+
use failure::Context;
12
pub use failure::{err_msg, Fail, Fallible, ResultExt};
23

4+
pub trait FailExt {
5+
fn downcast_ctx<T: Fail>(&self) -> Option<&T>;
6+
}
7+
8+
impl FailExt for dyn Fail {
9+
fn downcast_ctx<T: Fail>(&self) -> Option<&T> {
10+
if let Some(res) = self.downcast_ref::<T>() {
11+
Some(res)
12+
} else if let Some(ctx) = self.downcast_ref::<Context<T>>() {
13+
Some(ctx.get_context())
14+
} else {
15+
None
16+
}
17+
}
18+
}
19+
320
macro_rules! to_failure_compat {
421
($($error:path,)*) => {
522
pub trait ToFailureCompat<T> {

src/report/archives.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ mod tests {
100100
use flate2::read::GzDecoder;
101101
use mime::Mime;
102102
use report::DummyWriter;
103-
use results::{DatabaseDB, TestResult, WriteResults};
103+
use results::{DatabaseDB, FailureReason, TestResult, WriteResults};
104104
use std::io::Read;
105105
use tar::Archive;
106106

@@ -130,7 +130,7 @@ mod tests {
130130
results
131131
.record_result(&ex, &ex.toolchains[1], &crate1, || {
132132
info!("tc2 crate1");
133-
Ok(TestResult::BuildFail)
133+
Ok(TestResult::BuildFail(FailureReason::Unknown))
134134
}).unwrap();
135135
results
136136
.record_result(&ex, &ex.toolchains[0], &crate2, || {

src/report/html.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use mime;
44
use minifier;
55
use prelude::*;
66
use report::{archives::Archive, Comparison, CrateResult, ReportWriter, TestResults};
7-
use results::TestResult;
7+
use results::{FailureReason, TestResult};
88
use std::collections::HashMap;
99

1010
#[derive(Serialize)]
@@ -29,21 +29,50 @@ impl ResultColor for Comparison {
2929
Comparison::SameTestSkipped => Color::Striped("#72a156", "#80b65f"),
3030
Comparison::SameTestPass => Color::Single("#72a156"),
3131
Comparison::Error => Color::Single("#d77026"),
32+
Comparison::SpuriousRegressed => Color::Striped("#db3026", "#d5433b"),
33+
Comparison::SpuriousFixed => Color::Striped("#5630db", "#5d3dcf"),
3234
}
3335
}
3436
}
3537

3638
impl ResultColor for TestResult {
3739
fn color(&self) -> Color {
3840
match self {
39-
TestResult::BuildFail => Color::Single("#db3026"),
40-
TestResult::TestFail => Color::Single("#65461e"),
41+
TestResult::BuildFail(_) => Color::Single("#db3026"),
42+
TestResult::TestFail(_) => Color::Single("#65461e"),
4143
TestResult::TestSkipped | TestResult::TestPass => Color::Single("#62a156"),
4244
TestResult::Error => Color::Single("#d77026"),
4345
}
4446
}
4547
}
4648

49+
trait ResultName {
50+
fn name(&self) -> String;
51+
}
52+
53+
impl ResultName for FailureReason {
54+
fn name(&self) -> String {
55+
match self {
56+
FailureReason::Unknown => "failed".into(),
57+
FailureReason::Broken => "broken".into(),
58+
FailureReason::Timeout => "timed out".into(),
59+
FailureReason::OOM => "OOM".into(),
60+
}
61+
}
62+
}
63+
64+
impl ResultName for TestResult {
65+
fn name(&self) -> String {
66+
match self {
67+
TestResult::BuildFail(reason) => format!("build {}", reason.name()),
68+
TestResult::TestFail(reason) => format!("test {}", reason.name()),
69+
TestResult::TestSkipped => "test skipped".into(),
70+
TestResult::TestPass => "test passed".into(),
71+
TestResult::Error => "error".into(),
72+
}
73+
}
74+
}
75+
4776
#[derive(Serialize)]
4877
struct NavbarItem {
4978
label: &'static str,
@@ -90,6 +119,7 @@ struct ResultsContext<'a> {
90119

91120
comparison_colors: HashMap<Comparison, Color>,
92121
result_colors: HashMap<TestResult, Color>,
122+
result_names: HashMap<TestResult, String>,
93123
}
94124

95125
#[derive(Serialize)]
@@ -110,6 +140,7 @@ fn write_report<W: ReportWriter>(
110140
) -> Fallible<()> {
111141
let mut comparison_colors = HashMap::new();
112142
let mut result_colors = HashMap::new();
143+
let mut result_names = HashMap::new();
113144

114145
let mut categories = HashMap::new();
115146
for result in &res.crates {
@@ -118,19 +149,25 @@ fn write_report<W: ReportWriter>(
118149
continue;
119150
}
120151

121-
// Add the colors used in this run
152+
// Add the colors and names used in this run
122153
comparison_colors
123154
.entry(result.res)
124155
.or_insert_with(|| result.res.color());
125156
if let Some(ref run) = result.runs[0] {
126157
result_colors
127158
.entry(run.res)
128159
.or_insert_with(|| run.res.color());
160+
result_names
161+
.entry(run.res)
162+
.or_insert_with(|| run.res.name());
129163
}
130164
if let Some(ref run) = result.runs[1] {
131165
result_colors
132166
.entry(run.res)
133167
.or_insert_with(|| run.res.color());
168+
result_names
169+
.entry(run.res)
170+
.or_insert_with(|| run.res.name());
134171
}
135172

136173
let mut category = categories.entry(result.res).or_insert_with(Vec::new);
@@ -150,6 +187,7 @@ fn write_report<W: ReportWriter>(
150187

151188
comparison_colors,
152189
result_colors,
190+
result_names,
153191
};
154192

155193
info!("generating {}", to);

0 commit comments

Comments
 (0)