sweet_cli/bench/
bench_assert.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use anyhow::Result;
use clap::Parser;
use std::fs;
use std::process::Command;

/// Measure the compilation time for the assert! macro
///
/// For context of an average large project:
///
/// `egrep -r "assert[!_]" . | wc -l`
///
/// bevy: 7,000
/// wasm-bindgen: 3,000
/// rust: 50,000
///
/// ## Expect
/// 10 lines of 'expect' comilied in 0.53s, each line added 53.00ms
/// 100 lines of 'expect' comilied in 0.47s, each line added 4.70ms
/// 1000 lines of 'expect' comilied in 0.49s, each line added 0.49ms
/// 2000 lines of 'expect' comilied in 0.50s, each line added 0.25ms
/// 3000 lines of 'expect' comilied in 0.53s, each line added 0.18ms
/// 5000 lines of 'expect' comilied in 0.56s, each line added 0.11ms
/// 10000 lines of 'expect' comilied in 0.70s, each line added 0.07ms * consistency starts here
/// 100000 lines of 'expect' comilied in 5.37s, each line added 0.05ms
/// 20000 lines of 'expect' comilied in 1.06s, each line added 0.05ms
/// 500000 lines of 'expect' comilied in 44.00s, each line added 0.09ms
///
/// ## Assert
///
/// 10 lines of 'assert' comilied in 0.21s, each line added 21.00ms
/// 100 lines of 'assert' comilied in 0.23s, each line added 2.30ms
/// 1000 lines of 'assert' comilied in 1.54s, each line added 1.54ms * smallest
/// 2000 lines of 'assert' comilied in 4.92s, each line added 2.46ms
/// 3000 lines of 'assert' comilied in 11.61s, each line added 3.87ms
/// 5000 lines of 'assert' comilied in 26.96s, each line added 5.39ms * consistency starts here
/// 10000 lines of 'assert' comilied in 55.00s, each line added 5.50ms
/// 20000 lines of 'expect' comilied in 1.06s, each line added 0.05ms * this is incorrect, it actually took 10 mins
/// 100000... no way dude
#[derive(Debug, Parser)]
pub struct BenchAssert {
	#[arg(long, default_value_t = 1000)] // 1000 is the most gracious
	iterations: usize,
	#[arg(long)]
	expect_only: bool,
	#[arg(long)]
	assert_only: bool,
	/// no detectable difference
	#[arg(long)]
	release: bool,
	/// no detectable difference
	#[arg(long)]
	run: bool,
}

const BENCH_DIR: &str = "./tests";

impl BenchAssert {
	pub fn run(self) -> Result<()> {
		fs::create_dir_all(BENCH_DIR)?;

		if self.expect_only {
			self.run_expect()?;
		} else if self.assert_only {
			self.run_assert()?;
		} else {
			self.run_expect()?;
			self.run_assert()?;
		}

		Ok(())
	}


	fn run_assert(&self) -> Result<()> {
		self.create_iter_file(ASSERT_FILE_PATH, ASSERT_TEMPLATE, |i| {
			format!("\tassert_eq!({},{});\n", i, i)
		})?;
		self.bench_compile("assert")?;
		if self.run {
			self.bench_run("assert")?;
		}
		Ok(())
	}
	fn run_expect(&self) -> Result<()> {
		self.create_iter_file(EXPECT_FILE_PATH, EXPECT_TEMPLATE, |i| {
			format!("\texpect({},{});\n", i, i)
		})?;
		self.bench_compile("expect")?;
		if self.run {
			self.bench_run("expect")?;
		}
		Ok(())
	}

	fn create_iter_file(
		&self,
		file_path: &str,
		file_template: &str,
		mk_str: impl Fn(usize) -> String,
	) -> Result<()> {
		let mut iterations = String::new();
		for i in 0..self.iterations {
			iterations.push_str(&mk_str(i));
		}

		let output =
			String::from(file_template).replace("__iterations__", &iterations);

		fs::write(file_path, output)?;
		Ok(())
	}


	fn bench_compile(&self, test_name: &str) -> Result<()> {
		// let path = path::Path::new(BENCH_DIR).join(test_name);
		let mut command = Command::new("cargo");
		command.arg("build").arg("--test").arg(test_name);
		if self.release {
			command.arg("--release");
		}
		let output = command.output()?;

		let stderr = String::from_utf8_lossy(&output.stderr);

		let duration = stderr
			.lines()
			.find(|line| line.contains("Finished"))
			.expect("line not found")
			.split(" ")
			.last()
			.unwrap()
			.replace("s", "")
			.parse::<f64>()
			.unwrap();

		let time_per_iter = (duration / self.iterations as f64) * 1000.;

		println!(
			"{} lines of '{}' comilied in {:.2}s, each line added {:.2}ms",
			self.iterations, test_name, duration, time_per_iter
		);
		Ok(())
	}

	fn bench_run(&self, test_name: &str) -> Result<()> {
		let output = Command::new("cargo")
			.arg("test")
			.arg("--test")
			.arg(test_name)
			.arg("--")
			.arg("--nocapture")
			.output()?;
		let output = String::from_utf8_lossy(&output.stdout);
		println!("{}", output);

		let duration = output
			.lines()
			.find(|line| line.contains("__"))
			.and_then(|line| line.split("__").nth(1))
			.and_then(|num| num.parse::<f64>().ok())
			.expect("Failed to find and parse number");

		let time_per_iter = (duration / self.iterations as f64) * 1000.;

		println!(
			"{} lines of '{}' ran in {:.2}s, each line added {:.2}ms",
			self.iterations, test_name, duration, time_per_iter
		);

		Ok(())
	}
}

const ASSERT_FILE_PATH: &str = "./tests/assert.rs";
const ASSERT_TEMPLATE: &str = r#"
	use std::time::Instant;
	#[test]	
	fn main(){
  	let start = Instant::now();
__iterations__
		println!("__{:.2}__", start.elapsed().as_secs_f32());
}
"#;
const EXPECT_FILE_PATH: &str = "./tests/expect.rs";
const EXPECT_TEMPLATE: &str = r#"
	use std::time::Instant;
	#[test]
	fn main(){
  	let start = Instant::now();
__iterations__
		println!("__{:.2}__", start.elapsed().as_secs_f32());
	}

	fn expect(a: i32, b: i32) {
		if a != b {
			panic!("Expected {} but got {}", a, b);
		}
	}
"#;