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
use anyhow::Result;
use lumin::search::{SearchOptions, SearchResult, SearchResultLine, search_files};
use serial_test::serial;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use tempfile::tempdir;
mod test_helpers;
use test_helpers::{TEST_DIR, TestEnvironment};
#[cfg(test)]
mod search_sort_tests {
use super::*;
/// Test that search results are sorted by file path and line number
#[test]
#[serial]
fn test_search_results_sorted() -> Result<()> {
// Create a temporary directory
let temp_dir = tempdir()?;
// Create test files with patterns that will match
let file_paths = [
"b_file.txt", // Intentionally out of alphabetical order
"a_file.txt",
"c_file.txt",
];
for file_name in &file_paths {
let file_path = temp_dir.path().join(file_name);
let mut file = File::create(&file_path)?;
// Write content with line numbers out of order to test sorting
writeln!(file, "Line 1 - no match")?;
writeln!(file, "Line 2 - pattern to find")?; // Match on line 2
writeln!(file, "Line 3 - no match")?;
writeln!(file, "Line 4 - pattern to find")?; // Match on line 4
writeln!(file, "Line 5 - no match")?;
writeln!(file, "Line 6 - pattern to find")?; // Match on line 6
}
// Search for the pattern
let pattern = "pattern to find";
let options = SearchOptions::default();
let result = search_files(pattern, temp_dir.path(), &options)?;
// Check that we have the expected number of matches
// 3 files × 3 matches per file = 9 matches
assert_eq!(result.total_number, 9, "Expected 9 matches in total");
// Check that the results are sorted by file path
// First check file paths
let mut prev_path: Option<&PathBuf> = None;
let mut prev_line_number: Option<u64> = None;
for (i, line) in result.lines.iter().enumerate() {
// Check file path sorting
if let Some(prev) = prev_path {
assert!(
prev <= &line.file_path,
"Results not sorted by file path at index {}",
i
);
// If same file, check line number sorting
if prev == &line.file_path {
let prev_num = prev_line_number.unwrap();
assert!(
prev_num < line.line_number,
"Results not sorted by line number at index {}",
i
);
}
}
prev_path = Some(&line.file_path);
prev_line_number = Some(line.line_number);
}
// Assert that the first result is from a_file.txt (alphabetically first)
assert!(
result.lines[0]
.file_path
.to_string_lossy()
.contains("a_file.txt"),
"First result should be from a_file.txt, got: {}",
result.lines[0].file_path.display()
);
// Check the expected order of line numbers for the first file
assert_eq!(
result.lines[0].line_number, 2,
"First match in a_file.txt should be on line 2"
);
assert_eq!(
result.lines[1].line_number, 4,
"Second match in a_file.txt should be on line 4"
);
assert_eq!(
result.lines[2].line_number, 6,
"Third match in a_file.txt should be on line 6"
);
// Verify the sort_by_path_and_line method on an artificially unsorted result
let mut unsorted_result = SearchResult {
total_number: 6,
lines: vec![
SearchResultLine {
file_path: temp_dir.path().join("z_file.txt"),
line_number: 10,
line_content: "test".to_string(),
content_omitted: false,
is_context: false,
},
SearchResultLine {
file_path: temp_dir.path().join("a_file.txt"),
line_number: 5,
line_content: "test".to_string(),
content_omitted: false,
is_context: false,
},
SearchResultLine {
file_path: temp_dir.path().join("a_file.txt"),
line_number: 1,
line_content: "test".to_string(),
content_omitted: false,
is_context: false,
},
SearchResultLine {
file_path: temp_dir.path().join("z_file.txt"),
line_number: 3,
line_content: "test".to_string(),
content_omitted: false,
is_context: false,
},
SearchResultLine {
file_path: temp_dir.path().join("m_file.txt"),
line_number: 7,
line_content: "test".to_string(),
content_omitted: false,
is_context: false,
},
SearchResultLine {
file_path: temp_dir.path().join("m_file.txt"),
line_number: 2,
line_content: "test".to_string(),
content_omitted: false,
is_context: false,
},
],
};
// Sort the results
unsorted_result.sort_by_path_and_line();
// Verify that the sort worked correctly
// First check for file path order: a_file -> m_file -> z_file
assert!(
unsorted_result.lines[0]
.file_path
.to_string_lossy()
.contains("a_file.txt")
);
assert!(
unsorted_result.lines[2]
.file_path
.to_string_lossy()
.contains("m_file.txt")
);
assert!(
unsorted_result.lines[4]
.file_path
.to_string_lossy()
.contains("z_file.txt")
);
// Then check for line number order within each file
assert_eq!(unsorted_result.lines[0].line_number, 1); // a_file.txt line 1
assert_eq!(unsorted_result.lines[1].line_number, 5); // a_file.txt line 5
assert_eq!(unsorted_result.lines[2].line_number, 2); // m_file.txt line 2
assert_eq!(unsorted_result.lines[3].line_number, 7); // m_file.txt line 7
assert_eq!(unsorted_result.lines[4].line_number, 3); // z_file.txt line 3
assert_eq!(unsorted_result.lines[5].line_number, 10); // z_file.txt line 10
Ok(())
}
}