Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d143612

Browse files
authoredDec 4, 2019
Merge pull request #29 from qryxip/ja-all-enabled-add-a-tool-to-test-rustc-dep-option-generator
Add a tool to test `rustc-dep-option-generator`
2 parents 457a84d + e518f26 commit d143612

File tree

21 files changed

+373
-11
lines changed

21 files changed

+373
-11
lines changed
 

‎.cargo/config

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[alias]
2-
dep-tests = ["run", "--manifest-path", "./dep-tests/Cargo.toml", "--"]
2+
dep-tests = ["run", "--manifest-path", "./tools/dep-tests/Cargo.toml", "--"]
3+
test-with-generated-opts = ["run", "--manifest-path", "./tools/test-with-generated-opts/Cargo.toml", "--"]

‎.github/workflows/ci.yml

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,55 @@ jobs:
3737
command: fmt
3838
args: -- --check
3939

40-
- name: '`cargo fmt --manifest-path ./dep-tests/Cargo.toml -- --check`'
40+
- name: '`cargo fmt --manifest-path ./tools/dep-tests/Cargo.toml -- --check`'
4141
uses: actions-rs/cargo@v1
4242
with:
4343
command: fmt
44-
args: --manifest-path ./dep-tests/Cargo.toml -- --check
44+
args: --manifest-path ./tools/dep-tests/Cargo.toml -- --check
45+
46+
- name: '`cargo fmt --manifest-path ./tools/test-with-generated-opts/Cargo.toml -- --check`'
47+
uses: actions-rs/cargo@v1
48+
with:
49+
command: fmt
50+
args: --manifest-path ./tools/test-with-generated-opts/Cargo.toml -- --check
51+
52+
test-with-generated-opts:
53+
name: test-with-generated-opts
54+
runs-on: ubuntu-18.04
55+
56+
steps:
57+
- name: Checkout
58+
uses: actions/checkout@v1
59+
60+
- name: rust-toolchain
61+
uses: actions-rs/toolchain@v1
62+
with:
63+
toolchain: 1.38.0-x86_64-unknown-linux-gnu
64+
default: true
65+
profile: default
66+
67+
- name: '`cargo install --git https://github.com/rust-lang-ja/atcoder-rustc-dep-option-generator`'
68+
uses: actions-rs/cargo@v1
69+
with:
70+
command: install
71+
args: --git https://github.com/rust-lang-ja/atcoder-rustc-dep-option-generator
72+
73+
- name: '`cargo clippy --all-features --manifest-path ./tools/test-with-generated-opts/Cargo.toml -- -D warnings`'
74+
uses: actions-rs/cargo@v1
75+
with:
76+
command: clippy
77+
args: --all-features --manifest-path ./tools/test-with-generated-opts/Cargo.toml -- -D warnings
78+
79+
- name: '`cargo build --all-features --release`'
80+
uses: actions-rs/cargo@v1
81+
with:
82+
command: build
83+
args: --all-features --release
84+
85+
- name: '`cargo test-with-generated-opts`'
86+
uses: actions-rs/cargo@v1
87+
with:
88+
command: test-with-generated-opts
4589

4690
build:
4791
strategy:
@@ -147,18 +191,18 @@ jobs:
147191
command: run
148192
args: ${{ matrix.features }} --release
149193

150-
- name: '`cargo clippy ${{ matrix.features }} --manifest-path ./dep-tests/Cargo.toml -- -D warnings`'
194+
- name: '`cargo clippy ${{ matrix.features }} --manifest-path ./tools/dep-tests/Cargo.toml -- -D warnings`'
151195
uses: actions-rs/cargo@v1
152196
with:
153197
command: clippy
154-
args: ${{ matrix.features }} --manifest-path ./dep-tests/Cargo.toml -- -D warnings
198+
args: ${{ matrix.features }} --manifest-path ./tools/dep-tests/Cargo.toml -- -D warnings
155199
if: matrix.dep_tests
156200

157-
- name: '`cargo test ${{ matrix.features }} --manifest-path ./dep-tests/Cargo.toml --no-fail-fast`'
201+
- name: '`cargo test ${{ matrix.features }} --manifest-path ./tools/dep-tests/Cargo.toml --no-fail-fast`'
158202
uses: actions-rs/cargo@v1
159203
with:
160204
command: test
161-
args: ${{ matrix.features }} --manifest-path ./dep-tests/Cargo.toml --no-fail-fast
205+
args: ${{ matrix.features }} --manifest-path ./tools/dep-tests/Cargo.toml --no-fail-fast
162206
if: matrix.dep_tests
163207

164208
- name: '`cargo dep-tests --all-features -d 1`'

‎.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/target/
2-
/dep-tests/Cargo.lock
3-
/dep-tests/target/
1+
/tools/dep-tests/Cargo.lock
2+
/tools/test-with-generated-opts/Cargo.lock
3+
**/target/
44
**/*.rs.bk
55
**/*~

‎Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ version = "0.1.0"
66
edition = "2018"
77

88
[workspace]
9-
exclude = ["./dep-tests"]
9+
exclude = ["./tools"]
1010

1111
[[bin]]
1212
name = "main"

‎examples/arc065-c.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// https://atcoder.jp/contests/arc065/tasks/arc065_a
2+
3+
use lazy_static::lazy_static;
4+
use proconio::input;
5+
use proconio::marker::Bytes;
6+
use regex::bytes::Regex;
7+
8+
fn main() {
9+
input! {
10+
s: Bytes,
11+
}
12+
13+
lazy_static! {
14+
static ref R: Regex = Regex::new(r"\A(dream(er)?|eraser?)*\z").unwrap();
15+
};
16+
println!("{}", if R.is_match(&s) { "YES" } else { "NO" });
17+
}

‎examples/practice-a.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// https://atcoder.jp/contests/practice/tasks/practice_1
2+
3+
use proconio::input;
4+
5+
fn main() {
6+
input! {
7+
a: u32,
8+
b: u32,
9+
c: u32,
10+
s: String,
11+
}
12+
13+
println!("{} {}", a + b + c, s);
14+
}

‎examples/tests.ron

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(
2+
tests: {
3+
"practice-a": (
4+
name: "practice contest: A - Welcome to AtCoder",
5+
word_match: Exact,
6+
),
7+
"arc065-c": (
8+
name: "ABC049 / ARC065: C - 白昼夢 / Daydream",
9+
word_match: Exact,
10+
),
11+
}
12+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
erasedream
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dreameraser
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dreamerer
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
YES
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
YES
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NO
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
1
2+
2 3
3+
test
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
72
2+
128 256
3+
myonmyon
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6 test
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
456 myonmyon
File renamed without changes.
File renamed without changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "test-with-generated-opts"
3+
version = "0.0.0"
4+
edition = "2018"
5+
publish = false
6+
description = "Test with `rustc-dep-option-generator`."
7+
8+
[dependencies]
9+
anyhow = "1.0.25"
10+
env_logger = "0.7.1"
11+
indexmap = { version = "1.3.0", features = ["serde-1"] }
12+
itertools = "0.8.2"
13+
log = "0.4.8"
14+
ron = "0.5.1"
15+
serde = { version = "1.0.103", features = ["derive"] }
16+
serde_json = "1.0.42"
17+
shell-escape = "0.1.4"
18+
structopt = "0.3.5"
19+
tempdir = "0.3.7"
20+
which = { version = "3.1.0", default-features = false }
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
use anyhow::{anyhow, Context as _};
2+
use env_logger::fmt::Color;
3+
use indexmap::IndexMap;
4+
use itertools::Itertools as _;
5+
use log::{info, Level, LevelFilter};
6+
use serde::Deserialize;
7+
use structopt::StructOpt;
8+
use tempdir::TempDir;
9+
10+
use std::collections::{BTreeMap, HashMap};
11+
use std::env;
12+
use std::ffi::{OsStr, OsString};
13+
use std::fs::{self, File};
14+
use std::io::{self, Read as _, Write as _};
15+
use std::path::{Path, PathBuf};
16+
use std::process::{Command, Output, Stdio};
17+
use std::str::SplitWhitespace;
18+
use std::time::Instant;
19+
20+
#[derive(StructOpt, Debug)]
21+
struct Opt {}
22+
23+
fn main() -> anyhow::Result<()> {
24+
Opt::from_args();
25+
26+
env_logger::builder()
27+
.format(|buf, record| {
28+
let mut style = buf.style();
29+
let mut write_with_style = |color, bold, intense, value| -> _ {
30+
let value = style
31+
.set_color(color)
32+
.set_bold(bold)
33+
.set_intense(intense)
34+
.value(value);
35+
write!(buf, "{}", value)
36+
};
37+
write_with_style(Color::Black, false, true, "[")?;
38+
match record.level() {
39+
Level::Info => write_with_style(Color::Cyan, true, false, "INFO"),
40+
Level::Warn => write_with_style(Color::Yellow, true, false, "WARN"),
41+
Level::Error => write_with_style(Color::Red, true, false, "ERROR"),
42+
_ => unreachable!(),
43+
}?;
44+
write_with_style(Color::Black, false, true, "]")?;
45+
writeln!(buf, " {}", record.args())
46+
})
47+
.filter_module("test_with_generated_opts", LevelFilter::Info)
48+
.init();
49+
50+
let Tests { tests } = File::open("./examples/tests.ron")
51+
.map_err(anyhow::Error::from)
52+
.and_then(|h| ron::de::from_reader(h).map_err(Into::into))
53+
.with_context(|| "Failed to read ./examples/tests.ron")?;
54+
55+
let tempdir = TempDir::new("atcoder-rust-base-test-with-generated-opts")?;
56+
57+
let tests = tests
58+
.into_iter()
59+
.map(|(slug, Test { name, word_match })| {
60+
let src = Path::new("./examples").join(&slug).with_extension("rs");
61+
let testsets = Path::new("./examples/testsets").join(&slug);
62+
let binary = compile(&src, tempdir.path(), &slug)?;
63+
Ok((name, word_match, testsets, binary))
64+
})
65+
.collect::<anyhow::Result<Vec<_>>>()?;
66+
67+
for (name, word_match, testsets, binary) in tests {
68+
test(&name, word_match, &testsets, &binary)?;
69+
}
70+
Ok(())
71+
}
72+
73+
fn compile(src: &Path, tempdir: &Path, dir_name: &str) -> anyhow::Result<PathBuf> {
74+
fn run_command<S1: AsRef<OsStr>, S2: AsRef<OsStr>, I: IntoIterator<Item = S2>>(
75+
program: S1,
76+
args: I,
77+
) -> anyhow::Result<Vec<u8>> {
78+
let program = program.as_ref();
79+
let args = args.into_iter().collect::<Vec<_>>();
80+
81+
info!(
82+
"Running `{}{}`",
83+
shell_escape::escape(program.to_string_lossy()),
84+
args.iter()
85+
.map(AsRef::as_ref)
86+
.map(OsStr::to_string_lossy)
87+
.map(shell_escape::escape)
88+
.format_with("", |s, f| f(&format_args!(" {}", s))),
89+
);
90+
91+
let Output { status, stdout, .. } = Command::new(program)
92+
.args(&args)
93+
.stdin(Stdio::null())
94+
.stderr(Stdio::inherit())
95+
.output()?;
96+
97+
if !status.success() {
98+
return Err(anyhow!("{}: {}", program.to_string_lossy(), status));
99+
}
100+
Ok(stdout)
101+
}
102+
103+
let generated_opts = {
104+
let program = which::which("rustc-dep-option-generator")?;
105+
let stdout = run_command(&program, &["--format", "json"])?;
106+
serde_json::from_slice::<Vec<String>>(&stdout)
107+
.with_context(|| format!("{}: invalid output", program.to_string_lossy()))?
108+
};
109+
110+
let out = tempdir
111+
.join(dir_name)
112+
.with_extension(if cfg!(windows) { "exe" } else { "" });
113+
114+
let program = env::var_os("RUSTC")
115+
.map(Ok)
116+
.unwrap_or_else(|| which::which("rustc").map(Into::into))?;
117+
118+
let args = {
119+
let mut args = vec![
120+
OsString::from("--edition"),
121+
OsString::from("2018"),
122+
OsString::from("-C"),
123+
OsString::from("opt-level=3"),
124+
OsString::from("-o"),
125+
OsString::from(&out),
126+
];
127+
for opt in generated_opts {
128+
args.push(opt.into());
129+
}
130+
args.push(src.to_owned().into());
131+
args
132+
};
133+
134+
run_command(program, args)?;
135+
Ok(out)
136+
}
137+
138+
fn test(
139+
task_name: &str,
140+
word_match: WordMatch,
141+
testsets: &Path,
142+
binary: &Path,
143+
) -> anyhow::Result<()> {
144+
let testsets = {
145+
let find_files = |dir: &str| -> _ {
146+
fs::read_dir(testsets.join(dir))?
147+
.map(|entry| {
148+
let path = entry?.path();
149+
let name = path
150+
.file_stem()
151+
.unwrap_or_default()
152+
.to_string_lossy()
153+
.into_owned();
154+
Ok((name, path))
155+
})
156+
.collect::<io::Result<HashMap<_, _>>>()
157+
};
158+
159+
let (ins, outs) = (find_files("in")?, find_files("out")?);
160+
161+
ins.into_iter()
162+
.flat_map(|(stem, path_in)| {
163+
outs.get(&stem)
164+
.map(|path_out| (stem, (path_in, path_out.clone())))
165+
})
166+
.collect::<BTreeMap<_, _>>()
167+
};
168+
169+
info!("Testing {} for {:?}", binary.display(), task_name);
170+
171+
for (test_name, (path_in, path_out)) in testsets {
172+
fn read_to_string(path: &Path) -> anyhow::Result<String> {
173+
fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))
174+
}
175+
176+
let input = read_to_string(&path_in)?;
177+
let expected = read_to_string(&path_out)?;
178+
let start = Instant::now();
179+
180+
let mut child = Command::new(binary)
181+
.stdin(Stdio::piped())
182+
.stdout(Stdio::piped())
183+
.stderr(Stdio::inherit())
184+
.spawn()
185+
.with_context(|| format!("Failed to execute {}", binary.display()))?;
186+
187+
child.stdin.as_mut().unwrap().write_all(input.as_ref())?;
188+
let status = child.wait()?;
189+
let stop = Instant::now();
190+
let actual = {
191+
let mut actual = "".to_owned();
192+
child
193+
.stdout
194+
.as_mut()
195+
.unwrap()
196+
.read_to_string(&mut actual)
197+
.with_context(|| format!("{} outputted invalid UTF-8", binary.display()))?;
198+
actual
199+
};
200+
201+
let time = (stop - start).as_millis();
202+
let (expected, actual) = (expected.split_whitespace(), actual.split_whitespace());
203+
let verdict = if status.success() && word_match.accepts(expected, actual) {
204+
"AC"
205+
} else if status.success() {
206+
"WA"
207+
} else {
208+
"RE"
209+
};
210+
info!("{}: {} in {}ms", test_name, verdict, time);
211+
if verdict != "AC" {
212+
return Err(anyhow!("Test failed"));
213+
}
214+
}
215+
Ok(())
216+
}
217+
218+
#[derive(Debug, Deserialize)]
219+
struct Tests {
220+
tests: IndexMap<String, Test>,
221+
}
222+
223+
#[derive(Debug, Deserialize)]
224+
struct Test {
225+
name: String,
226+
word_match: WordMatch,
227+
}
228+
229+
#[derive(Debug, Clone, Copy, Deserialize)]
230+
enum WordMatch {
231+
Exact,
232+
}
233+
234+
impl WordMatch {
235+
fn accepts(self, expected: SplitWhitespace, actual: SplitWhitespace) -> bool {
236+
match self {
237+
WordMatch::Exact => itertools::equal(expected, actual),
238+
}
239+
}
240+
}

0 commit comments

Comments
 (0)
Please sign in to comment.