Skip to content

Commit e012efc

Browse files
committed
Let's try testing for "is not quadratic" condition
1 parent 00cdbce commit e012efc

File tree

3 files changed

+100
-1
lines changed

3 files changed

+100
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
run: cargo test --no-run --locked
7777

7878
- name: Test
79-
run: cargo test
79+
run: cargo test -- --nocapture
8080

8181
- name: Prepare cache
8282
run: cargo xtask pre-cache

crates/ide/src/syntax_highlighting/tests.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
use std::time::Instant;
2+
13
use expect_test::{expect_file, ExpectFile};
24
use ide_db::SymbolKind;
5+
use stdx::format_to;
36
use test_utils::{bench, bench_fixture, skip_slow_tests};
47

58
use crate::{fixture, FileRange, HlTag, TextRange};
@@ -257,6 +260,99 @@ fn benchmark_syntax_highlighting_long_struct() {
257260
assert_eq!(hash, 2001);
258261
}
259262

263+
#[test]
264+
fn syntax_highlighting_not_quadratic() {
265+
if skip_slow_tests() {
266+
return;
267+
}
268+
269+
let mut measures = Vec::new();
270+
for i in 6..=10 {
271+
let n = 1 << i;
272+
let fixture = bench_fixture::big_struct_n(n);
273+
let (analysis, file_id) = fixture::file(&fixture);
274+
275+
let time = Instant::now();
276+
277+
let hash = analysis
278+
.highlight(file_id)
279+
.unwrap()
280+
.iter()
281+
.filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
282+
.count();
283+
assert!(hash > n as usize);
284+
285+
let elapsed = time.elapsed();
286+
measures.push((n as f64, elapsed.as_millis() as f64))
287+
}
288+
289+
assert_linear(&measures)
290+
}
291+
292+
/// Checks that a set of measurements looks like a liner function rather than
293+
/// like a quadratic function. Algorithm:
294+
///
295+
/// 1. Linearly scale input to be in [0; 1)
296+
/// 2. Using linear regression, compute the best linear function approximating
297+
/// the input.
298+
/// 3. Compute RMSE and maximal absolute error.
299+
/// 4. Check that errors are within tolerances and that the constant term is not
300+
/// too negative.
301+
///
302+
/// Ideally, we should use a proper "model selection" to directly compare
303+
/// quadratic and linear models, but that sounds rather complicated:
304+
///
305+
/// https://stats.stackexchange.com/questions/21844/selecting-best-model-based-on-linear-quadratic-and-cubic-fit-of-data
306+
fn assert_linear(xy: &[(f64, f64)]) {
307+
let (mut xs, mut ys): (Vec<_>, Vec<_>) = xy.iter().copied().unzip();
308+
normalize(&mut xs);
309+
normalize(&mut ys);
310+
let xy = xs.iter().copied().zip(ys.iter().copied());
311+
312+
// Linear regression: finding a and b to fit y = a + b*x.
313+
314+
let mean_x = mean(&xs);
315+
let mean_y = mean(&ys);
316+
317+
let b = {
318+
let mut num = 0.0;
319+
let mut denom = 0.0;
320+
for (x, y) in xy.clone() {
321+
num += (x - mean_x) * (y - mean_y);
322+
denom += (x - mean_x).powi(2);
323+
}
324+
num / denom
325+
};
326+
327+
let a = mean_y - b * mean_x;
328+
329+
let mut plot = format!("y_pred = {:.3} + {:.3} * x\n\nx y y_pred\n", a, b);
330+
331+
let mut se = 0.0;
332+
let mut max_error = 0.0f64;
333+
for (x, y) in xy {
334+
let y_pred = a + b * x;
335+
se += (y - y_pred).powi(2);
336+
max_error = max_error.max((y_pred - y).abs());
337+
338+
format_to!(plot, "{:.3} {:.3} {:.3}\n", x, y, y_pred);
339+
}
340+
341+
let rmse = (se / xs.len() as f64).sqrt();
342+
format_to!(plot, "\nrmse = {:.3} max error = {:.3}", rmse, max_error);
343+
344+
assert!(rmse < 0.05 && max_error < 0.1 && a > -0.1, "\nLooks quadratic\n{}", plot);
345+
346+
fn normalize(xs: &mut Vec<f64>) {
347+
let max = xs.iter().copied().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
348+
xs.iter_mut().for_each(|it| *it /= max);
349+
}
350+
351+
fn mean(xs: &[f64]) -> f64 {
352+
xs.iter().copied().sum::<f64>() / (xs.len() as f64)
353+
}
354+
}
355+
260356
#[test]
261357
fn benchmark_syntax_highlighting_parser() {
262358
if skip_slow_tests() {

crates/test_utils/src/bench_fixture.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use crate::project_root;
88

99
pub fn big_struct() -> String {
1010
let n = 1_000;
11+
big_struct_n(n)
12+
}
1113

14+
pub fn big_struct_n(n: u32) -> String {
1215
let mut buf = "pub struct RegisterBlock {".to_string();
1316
for i in 0..n {
1417
format_to!(buf, " /// Doc comment for {}.\n", i);

0 commit comments

Comments
 (0)