1
+ """
2
+ This is bound very closely to the current implementation of
3
+ the tests in `test/line_attribution_tests.
4
+
5
+ The two things that matter are the number of loops, the size
6
+ of the allocations, and the exact line numbers.
7
+
8
+
9
+ """
10
+
11
+ expected_md5_sums = {
12
+ "line_attribution_tests/loop_below_threshold.py" : "7664a7dcc0f4ab94a44e431448b5f348" ,
13
+ "line_attribution_tests/loop_with_one_alloc.py" : "da9ff0aa223123c956049e940c3ef093" ,
14
+ "line_attribution_tests/loop_with_multiple_lines.py" : "48ce0e8693fe43b1ebb7eb75a0fd5832" ,
15
+ "line_attribution_tests/loop_with_two_allocs.py" : "71f337140aa25383525e56a6167cabf8" ,
16
+ "line_attribution_tests/line_after_final_alloc.py" : "ca8cdd44ea6e4a9c286c05facae6a721"
17
+ }
18
+
19
+ import subprocess
20
+ import tempfile
21
+ import sys
22
+ from typing import List
23
+ from pathlib import Path
24
+ from hashlib import md5
25
+ from scalene .scalene_json import ScaleneJSONSchema
26
+
27
+ N_LOOPS = 31
28
+ LOOP_ALLOC_LINENO = 5 #
29
+ OUT_OF_LOOP_ALLOC_LINENO = 9
30
+
31
+ def check_for_changes ():
32
+ errors = []
33
+ for fname , expected_sum in expected_md5_sums .items ():
34
+ with open (fname , 'rb' ) as f :
35
+ digest = md5 (f .read ()).hexdigest ()
36
+ if digest != expected_sum :
37
+ errors .append (fname )
38
+ assert len (errors ) == 0 , f'Detected change in file(s) { ',' .join (errors )} '
39
+
40
+ def get_line (scalene_profile : ScaleneJSONSchema , lineno : int ):
41
+ files = list (scalene_profile .files .keys ())
42
+ assert len (files ) == 1
43
+ filename = files [0 ]
44
+ return scalene_profile .files [filename ].lines [lineno - 1 ]
45
+
46
+
47
+
48
+
49
+ def get_profile (test_stem , outdir_p , test_dir ):
50
+ proc = subprocess .run (
51
+ [
52
+ sys .executable ,
53
+ "-m" ,
54
+ "scalene" ,
55
+ "--cli" ,
56
+ "--json" ,
57
+ "--outfile" ,
58
+ outdir_p / f"{ test_stem } .json" ,
59
+ test_dir / f"{ test_stem } .py" ,
60
+ ],
61
+ capture_output = True ,
62
+ check = True ,
63
+ )
64
+ with open (outdir_p / f"{ test_stem } .json" , "r" ) as f :
65
+ profile = ScaleneJSONSchema .model_validate_json (f .read ())
66
+ return (test_stem , profile )
67
+
68
+
69
+ def main ():
70
+ test_dir = Path (__file__ ).parent / "line_attribution_tests"
71
+ with tempfile .TemporaryDirectory () as outdir :
72
+ outdir_p = Path (outdir )
73
+ one_alloc = get_profile ("loop_with_one_alloc" , outdir_p , test_dir )
74
+ two_on_one_line = get_profile ("loop_with_two_allocs" , outdir_p , test_dir )
75
+ below_threshold = get_profile ("loop_below_threshold" , outdir_p , test_dir )
76
+ line_after_final_alloc = get_profile (
77
+ "line_after_final_alloc" , outdir_p , test_dir
78
+ )
79
+ errors = []
80
+ for stem , profile in [one_alloc , two_on_one_line , line_after_final_alloc ]:
81
+ line = get_line (profile , LOOP_ALLOC_LINENO )
82
+ if not line .n_mallocs == N_LOOPS :
83
+ errors .append (f"Expected { N_LOOPS } distinct lines on { stem } , got { line .n_mallocs } on { LOOP_ALLOC_LINENO } " )
84
+
85
+ bt_stem , bt_prof = below_threshold
86
+ bt_line = get_line (bt_prof , LOOP_ALLOC_LINENO )
87
+ if not bt_line .n_mallocs < N_LOOPS :
88
+ errors .append (f"{ bt_stem } makes smaller allocations than the allocation sampling window, so fewer than { N_LOOPS } allocations on { LOOP_ALLOC_LINENO } should be reported. Got { bt_line .n_mallocs } " )
89
+
90
+ for stem , profile in [one_alloc , two_on_one_line , below_threshold , line_after_final_alloc ]:
91
+ line = get_line (profile , OUT_OF_LOOP_ALLOC_LINENO )
92
+ if not line .n_mallocs == 1 :
93
+ errors .append (f'Line { OUT_OF_LOOP_ALLOC_LINENO } in { stem } makes a large allocation, so it should be reported.' )
94
+
95
+ if len (errors ) > 0 :
96
+ for error in errors :
97
+ print (f'ERROR: { error } ' )
98
+ exit (1 )
99
+ else :
100
+ print ("PASS" )
101
+ exit (0 )
102
+
103
+ if __name__ == '__main__' :
104
+ main ()
0 commit comments