Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
649 linesCodeBlameRaw
1//! Exercise the production transition SQL with interleaved attempts.
2use rusqlite::{Connection, OptionalExtension, params};
3
4use super::{
5 ABANDON_LANDING, ACCEPT_BUILD, BEGIN_LANDING, CLAIM_BUILD, ENQUEUE, FINISH_CHECKS,
6 FINISH_LANDING, LEAVE, PUBLISHING, REPORT_BUILD, RETEST, RETESTED, STILL_LANDING,
7 attempt_branch,
8};
9use crate::integration::{FINISH_PROBE, REPORT_PROBE, RESOLVE_HELD};
10
11fn database() -> Connection {
12 let db = Connection::open_in_memory().unwrap();
13 db.execute_batch("PRAGMA foreign_keys = ON").unwrap();
14 let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("migrations");
15 let mut paths: Vec<_> = std::fs::read_dir(dir)
16 .unwrap()
17 .map(|entry| entry.unwrap().path())
18 .filter(|path| path.extension().is_some_and(|ext| ext == "sql"))
19 .collect();
20 paths.sort();
21 for path in paths {
22 db.execute_batch(&std::fs::read_to_string(path).unwrap())
23 .unwrap();
24 }
25 db.execute_batch("INSERT INTO pulls (id, repo_id, number, title, agent, runtime, author_id, author_name, status, created_at, updated_at)
26 VALUES ('pull', 'repo', 1, 'Change', 'codex', 'external', 'user', 'user', 'open', 'now', 'now');
27 INSERT INTO queue_entries (id, state, repo_id, pull_id, number, enqueued_by, created_at)
28 VALUES ('entry', 'waiting', 'repo', 'pull', 1, '{}', 'now');").unwrap();
29 db
30}
31
32#[test]
33fn repeated_admission_is_atomic() {
34 let db = database();
35 db.execute("DELETE FROM queue_entries", []).unwrap();
36 let enqueue = |id: &str| {
37 db.query_row(
38 ENQUEUE,
39 params![id, "repo", "pull", 1, "{}", 0, "later", 0, "merge", None::<String>, None::<String>],
40 |_| Ok(()),
41 )
42 .optional()
43 .unwrap()
44 .is_some()
45 };
46 assert!(enqueue("first"));
47 assert!(!enqueue("second"));
48 db.execute(
49 "UPDATE queue_entries SET state = 'removed' WHERE id = 'first'",
50 [],
51 )
52 .unwrap();
53 assert!(enqueue("third"));
54}
55
56#[test]
57fn stale_reset_and_held_failure_cannot_touch_a_replacement_attempt() {
58 let db = database();
59 assert!(claim(&db, "old"));
60 assert!(report(&db, "old", "old-commit"));
61 db.execute("UPDATE queue_entries SET held = 'failure'", [])
62 .unwrap();
63 assert_eq!(
64 db.execute(RETEST, params!["entry", "old"])
65 .unwrap(),
66 1
67 );
68 assert!(claim(&db, "new"));
69 assert!(report(&db, "new", "new-commit"));
70 db.execute("UPDATE queue_entries SET held = 'failure'", [])
71 .unwrap();
72 assert_eq!(
73 db.execute(RETEST, params!["entry", "old"])
74 .unwrap(),
75 0
76 );
77 assert!(
78 db.query_row(
79 RESOLVE_HELD,
80 params!["now", "entry", "failure", "old"],
81 |_| Ok(())
82 )
83 .optional()
84 .unwrap()
85 .is_none()
86 );
87 assert!(
88 db.query_row(
89 RESOLVE_HELD,
90 params!["now", "entry", "failure", "new"],
91 |_| Ok(())
92 )
93 .optional()
94 .unwrap()
95 .is_some()
96 );
97 assert_ne!(
98 attempt_branch("entry", Some("old")),
99 attempt_branch("entry", Some("new"))
100 );
101}
102
103#[test]
104fn removal_and_publication_have_one_winner_and_recovery_keeps_the_commit() {
105 let db = database();
106 assert!(claim(&db, "attempt"));
107 assert!(report(&db, "attempt", "tested"));
108 assert!(finish(&db, "tested"));
109 let publish = || {
110 db.query_row(BEGIN_LANDING, params!["entry", "tested", "attempt"], |_| {
111 Ok(())
112 })
113 .optional()
114 .unwrap()
115 .is_some()
116 };
117 assert!(publish());
118 assert!(publish(), "a crash resumes the same immutable publication");
119 assert!(
120 db.query_row(
121 LEAVE,
122 params!["removed", None::<String>, "now", "entry"],
123 |_| Ok(())
124 )
125 .optional()
126 .unwrap()
127 .is_none()
128 );
129 assert_eq!(
130 db.execute(RETEST, params!["entry", "attempt"])
131 .unwrap(),
132 0
133 );
134 assert!(
135 db.query_row(
136 BEGIN_LANDING,
137 params!["entry", "different", "attempt"],
138 |_| Ok(())
139 )
140 .optional()
141 .unwrap()
142 .is_none()
143 );
144 db.execute("UPDATE queue_entries SET landing_commit = NULL", [])
145 .unwrap();
146 assert!(
147 db.query_row(
148 LEAVE,
149 params!["removed", None::<String>, "now", "entry"],
150 |_| Ok(())
151 )
152 .optional()
153 .unwrap()
154 .is_some()
155 );
156 assert!(!publish());
157}
158
159#[test]
160fn removing_a_predecessor_invalidates_the_same_attempt_even_if_it_just_passed() {
161 let db = database();
162 assert!(claim(&db, "attempt"));
163 // Removal read this attempt while it was building; its result arrives
164 // before the dependent entry's reset reaches the database.
165 assert!(report(&db, "attempt", "includes-removed-predecessor"));
166 assert!(finish(&db, "includes-removed-predecessor"));
167 assert_eq!(db.execute(RETEST, params!["entry", "attempt"]).unwrap(), 1);
168 assert!(!finish(&db, "includes-removed-predecessor"));
169 assert!(claim(&db, "replacement"));
170 assert_eq!(db.execute(RETEST, params!["entry", "attempt"]).unwrap(), 0);
171}
172
173#[test]
174fn a_passing_entry_cannot_publish_ahead_of_an_unfinished_predecessor() {
175 let db = database();
176 assert!(claim(&db, "attempt"));
177 assert!(report(&db, "attempt", "tested"));
178 assert!(finish(&db, "tested"));
179 db.execute("INSERT INTO queue_entries (id, repo_id, pull_id, number, enqueued_by, state, created_at) VALUES ('earlier', 'repo', 'other', 2, '{}', 'testing', 'before')", []).unwrap();
180 db.execute("UPDATE queue_entries SET ahead = '[2]' WHERE id = 'entry'", []).unwrap();
181 assert!(
182 db.query_row(
183 BEGIN_LANDING,
184 params!["entry", "tested", "attempt"],
185 |_| Ok(())
186 )
187 .optional()
188 .unwrap()
189 .is_none()
190 );
191 db.execute("UPDATE queue_entries SET state = 'removed' WHERE id = 'earlier'", []).unwrap();
192 assert!(db.query_row(BEGIN_LANDING, params!["entry", "tested", "attempt"], |_| Ok(())).optional().unwrap().is_none(),
193 "removing the predecessor cannot publish a combination that still contains it");
194 db.execute(
195 "UPDATE queue_entries SET state = 'landed' WHERE id = 'earlier'",
196 [],
197 )
198 .unwrap();
199 assert!(
200 db.query_row(
201 BEGIN_LANDING,
202 params!["entry", "tested", "attempt"],
203 |_| Ok(())
204 )
205 .optional()
206 .unwrap()
207 .is_some()
208 );
209}
210
211#[test]
212fn delayed_probe_reports_and_timeouts_cannot_replace_accepted_results() {
213 let db = database();
214 db.execute("INSERT INTO queue_probes (id, repo_id, entry_id, number, subset, state, token_hash, created_at) VALUES ('probe', 'repo', 'entry', 1, '[]', 'testing', 'token', 'now')", []).unwrap();
215 assert!(
216 db.query_row(
217 REPORT_PROBE,
218 params!["tested", "probe", "token"],
219 |_| Ok(())
220 )
221 .optional()
222 .unwrap()
223 .is_some()
224 );
225 assert!(
226 db.query_row(
227 REPORT_PROBE,
228 params!["different", "probe", "token"],
229 |_| Ok(())
230 )
231 .optional()
232 .unwrap()
233 .is_none()
234 );
235 let finish = |token: Option<&str>, commit: Option<&str>| {
236 db.query_row(
237 FINISH_PROBE,
238 params![
239 "passed",
240 None::<String>,
241 "now",
242 "probe",
243 "testing",
244 token,
245 commit
246 ],
247 |_| Ok(()),
248 )
249 .optional()
250 .unwrap()
251 .is_some()
252 };
253 assert!(
254 !finish(Some("token"), None),
255 "timeout from before report acceptance"
256 );
257 assert!(!finish(None, Some("different")));
258 assert!(finish(None, Some("tested")));
259 assert!(!finish(None, Some("tested")));
260}
261
262fn claim(db: &Connection, token: &str) -> bool {
263 db.query_row(
264 CLAIM_BUILD,
265 params![token, "head", "base", "[]", "now", "entry", "[]"],
266 |_| Ok(()),
267 )
268 .optional()
269 .unwrap()
270 .is_some()
271}
272
273fn report(db: &Connection, token: &str, commit: &str) -> bool {
274 if db
275 .query_row(ACCEPT_BUILD, params![commit, "entry", token], |_| Ok(()))
276 .optional()
277 .unwrap()
278 .is_none()
279 {
280 return false;
281 }
282 db.query_row(
283 REPORT_BUILD,
284 params![
285 "testing",
286 commit,
287 "[]",
288 None::<String>,
289 None::<String>,
290 0,
291 "testing",
292 "now",
293 "entry",
294 token,
295 commit
296 ],
297 |_| Ok(()),
298 )
299 .optional()
300 .unwrap()
301 .is_some()
302}
303
304#[test]
305fn a_workflow_finishing_during_dispatch_finds_the_accepted_commit() {
306 let db = database();
307 assert!(claim(&db, "attempt"));
308 assert!(
309 db.query_row(ACCEPT_BUILD, params!["tested", "entry", "attempt"], |_| Ok(
310 ()
311 ))
312 .optional()
313 .unwrap()
314 .is_some()
315 );
316 assert!(
317 !finish(&db, "tested"),
318 "callbacks wait until every workflow is registered"
319 );
320 assert!(
321 db.query_row(
322 REPORT_BUILD,
323 params![
324 "testing",
325 "tested",
326 "[]",
327 None::<String>,
328 None::<String>,
329 0,
330 "testing",
331 "now",
332 "entry",
333 "attempt",
334 "tested"
335 ],
336 |_| Ok(())
337 )
338 .optional()
339 .unwrap()
340 .is_some()
341 );
342 // report_queue rereads the group's facts after opening this fence.
343 assert!(finish(&db, "tested"));
344 assert!(
345 db.query_row(
346 REPORT_BUILD,
347 params![
348 "testing",
349 "tested",
350 "[]",
351 None::<String>,
352 None::<String>,
353 0,
354 "testing",
355 "now",
356 "entry",
357 "attempt",
358 "tested"
359 ],
360 |_| Ok(())
361 )
362 .optional()
363 .unwrap()
364 .is_none()
365 );
366}
367
368fn finish(db: &Connection, commit: &str) -> bool {
369 db.query_row(
370 FINISH_CHECKS,
371 // As merge_group_finished binds it: the attempt of the row it read.
372 params!["passed", None::<String>, "passed", "now", "entry", commit, current_attempt(db)],
373 |_| Ok(()),
374 )
375 .optional()
376 .unwrap()
377 .is_some()
378}
379
380#[test]
381fn only_the_scheduler_that_claimed_an_entry_may_start_its_build() {
382 let db = database();
383 assert!(claim(&db, "first"));
384 assert!(!claim(&db, "second"));
385 assert!(!report(&db, "second", "wrong"));
386 assert!(report(&db, "first", "combined"));
387 assert!(!report(&db, "first", "combined"));
388 assert!(finish(&db, "combined"));
389 assert!(!finish(&db, "combined"));
390}
391
392#[test]
393fn a_result_read_before_removal_cannot_resurrect_the_entry() {
394 let db = database();
395 assert!(claim(&db, "first"));
396 // report_queue has read the token, then the user removes the entry.
397 db.execute("UPDATE queue_entries SET state = 'removed'", [])
398 .unwrap();
399 assert!(!report(&db, "first", "combined"));
400 assert_eq!(
401 db.query_row("SELECT state FROM queue_entries", [], |row| row
402 .get::<_, String>(0))
403 .unwrap(),
404 "removed"
405 );
406}
407
408#[test]
409fn an_old_build_cannot_overwrite_a_retry() {
410 let db = database();
411 assert!(claim(&db, "old"));
412 db.execute(
413 "UPDATE queue_entries SET state = 'waiting', token_hash = NULL",
414 [],
415 )
416 .unwrap();
417 assert!(!report(&db, "old", "old-commit"));
418 assert!(claim(&db, "new"));
419 assert!(!report(&db, "old", "old-commit"));
420 assert!(report(&db, "new", "new-commit"));
421 assert!(finish(&db, "new-commit"));
422}
423
424#[test]
425fn old_workflow_callbacks_cannot_pass_a_new_attempt() {
426 let db = database();
427 assert!(claim(&db, "old"));
428 assert!(report(&db, "old", "old-commit"));
429 // The callback read this row before a retry reset it.
430 db.execute(
431 "UPDATE queue_entries SET state = 'waiting', combined_commit = NULL",
432 [],
433 )
434 .unwrap();
435 assert!(claim(&db, "new"));
436 assert!(!finish(&db, "old-commit"));
437 assert!(report(&db, "new", "new-commit"));
438 assert!(!finish(&db, "old-commit"));
439 assert!(finish(&db, "new-commit"));
440}
441
442fn exists(db: &Connection, sql: &str, args: impl rusqlite::Params) -> bool {
443 db.query_row(sql, args, |_| Ok(())).optional().unwrap().is_some()
444}
445
446#[test]
447fn a_publisher_that_lost_the_race_cannot_undo_the_winners_landing() {
448 let db = database();
449 assert!(claim(&db, "attempt"));
450 assert!(report(&db, "attempt", "tested"));
451 assert!(finish(&db, "tested"));
452 // Two settle passes (a poll and a callback) both take the intent: it is
453 // resumable by design, so it is not exclusive.
454 assert!(exists(&db, BEGIN_LANDING, params!["entry", "tested", "attempt"]));
455 assert!(exists(&db, BEGIN_LANDING, params!["entry", "tested", "attempt"]));
456 // Mid-flight, the loser's failed compare-and-swap cannot tell it apart
457 // from the winner: it must ask Git again (settle) before abandoning.
458 assert!(exists(&db, STILL_LANDING, params!["entry", "tested"]));
459 // The winner records the merge and finishes, dropping the tested branch;
460 // the loser's retry then fails for want of that branch.
461 assert_eq!(db.execute(FINISH_LANDING, params!["now", "entry"]).unwrap(), 1);
462 assert!(!exists(&db, STILL_LANDING, params!["entry", "tested"]));
463 assert!(
464 !exists(&db, ABANDON_LANDING, params!["entry", "tested"]),
465 "only a holder of the intent may give it up, so nothing is retested"
466 );
467 assert_eq!(db.execute(RETEST, params!["entry", "attempt"]).unwrap(), 0);
468 assert_eq!(
469 db.query_row("SELECT state FROM queue_entries", [], |row| row.get::<_, String>(0)).unwrap(),
470 "landed"
471 );
472}
473
474#[test]
475fn a_genuinely_refused_publication_is_abandoned_once() {
476 let db = database();
477 assert!(claim(&db, "attempt"));
478 assert!(report(&db, "attempt", "tested"));
479 assert!(finish(&db, "tested"));
480 assert!(exists(&db, BEGIN_LANDING, params!["entry", "tested", "attempt"]));
481 assert!(exists(&db, PUBLISHING, params!["pull"]), "removal, closing and rebasing are refused");
482 assert!(exists(&db, ABANDON_LANDING, params!["entry", "tested"]));
483 assert!(!exists(&db, ABANDON_LANDING, params!["entry", "tested"]));
484 assert!(!exists(&db, PUBLISHING, params!["pull"]));
485 assert_eq!(db.execute(RETEST, params!["entry", "attempt"]).unwrap(), 1);
486}
487
488#[test]
489fn only_a_reset_attempt_gives_up_its_branch() {
490 let db = database();
491 assert!(claim(&db, "attempt"));
492 assert!(report(&db, "attempt", "tested"));
493 assert!(finish(&db, "tested"));
494 assert!(exists(&db, BEGIN_LANDING, params!["entry", "tested", "attempt"]));
495 // Being published: the reset does nothing and the branch must stay.
496 assert_eq!(db.execute(RETEST, params!["entry", "attempt"]).unwrap(), 0);
497 assert!(!exists(&db, RETESTED, params!["entry", "attempt"]));
498 assert!(exists(&db, ABANDON_LANDING, params!["entry", "tested"]));
499 assert_eq!(db.execute(RETEST, params!["entry", "attempt"]).unwrap(), 1);
500 assert!(exists(&db, RETESTED, params!["entry", "attempt"]));
501 // A newer attempt's branch is never the reset one's.
502 assert!(claim(&db, "next"));
503 assert!(!exists(&db, RETESTED, params!["entry", "attempt"]));
504}
505
506#[test]
507fn a_bypass_asked_for_at_admission_is_kept_for_landing() {
508 let db = database();
509 db.execute("DELETE FROM queue_entries", []).unwrap();
510 assert!(exists(&db, ENQUEUE, params!["first", "repo", "pull", 1, "{}", 0, "later", 1, "merge", None::<String>, None::<String>]));
511 assert_eq!(
512 db.query_row("SELECT bypass_rules FROM queue_entries", [], |row| row.get::<_, u32>(0)).unwrap(),
513 1
514 );
515 let person = g1t_contracts::User { id: "u".into(), username: "u".into(), ..Default::default() };
516 assert!(crate::rulesets::landing_honors_bypass(&person, true));
517 assert!(!crate::rulesets::landing_honors_bypass(&person, false));
518 assert!(crate::rulesets::landing_honors_bypass(&g1t_contracts::User::system("ns"), false));
519}
520
521fn current_attempt(db: &Connection) -> Option<String> {
522 db.query_row("SELECT attempt_id FROM queue_entries WHERE id = 'entry'", [], |row| row.get(0)).unwrap()
523}
524
525#[test]
526fn a_callback_read_for_an_earlier_attempt_cannot_finish_a_retry_on_the_same_commit() {
527 let db = database();
528 assert!(claim(&db, "old"));
529 assert!(report(&db, "old", "same"));
530 assert_eq!(db.execute(RETEST, params!["entry", "old"]).unwrap(), 1);
531 assert!(claim(&db, "new"));
532 assert!(report(&db, "new", "same"));
533 let finish_as = |attempt: &str| {
534 exists(&db, FINISH_CHECKS, params!["failed", None::<String>, "failed", "now", "entry", "same", attempt])
535 };
536 assert!(!finish_as("old"), "the row it read belonged to the earlier attempt");
537 assert!(finish_as("new"));
538}
539
540#[test]
541fn each_attempt_records_exactly_the_stack_it_built() {
542 let db = database();
543 let stack = |commit: &str| {
544 serde_json::json!([{ "number": 1, "title": "Change", "source": { "namespace": "ns", "name": "repo" },
545 "branch": "feature", "commit": commit }])
546 .to_string()
547 };
548 let claim_with = |token: &str, built: &str| {
549 exists(&db, CLAIM_BUILD, params![token, built, "base", "[]", "now", "entry", stack(built)])
550 };
551 assert!(claim_with("first", "c1"));
552 assert_eq!(db.execute(RETEST, params!["entry", "first"]).unwrap(), 1);
553 assert!(claim_with("second", "c2"));
554 let row: super::EntryRow = {
555 let mut statement = db.prepare("SELECT * FROM queue_entries WHERE id = 'entry'").unwrap();
556 let names: Vec<String> = statement.column_names().iter().map(|name| (*name).to_owned()).collect();
557 statement
558 .query_row([], |row| {
559 let mut object = serde_json::Map::new();
560 for (index, name) in names.iter().enumerate() {
561 let value: rusqlite::types::Value = row.get(index)?;
562 object.insert(name.clone(), match value {
563 rusqlite::types::Value::Null => serde_json::Value::Null,
564 rusqlite::types::Value::Integer(n) => n.into(),
565 rusqlite::types::Value::Real(n) => n.into(),
566 rusqlite::types::Value::Text(text) => text.into(),
567 rusqlite::types::Value::Blob(_) => serde_json::Value::Null,
568 });
569 }
570 Ok(serde_json::from_value(serde_json::Value::Object(object)).unwrap())
571 })
572 .unwrap()
573 };
574 let recorded = row.tested_stack().unwrap();
575 assert_eq!(recorded[0].commit, "c2", "the second attempt's own commits");
576 assert!(crate::integration::recorded_stack(recorded.clone(), 1, &[]).is_some());
577 assert!(crate::integration::recorded_stack(recorded, 1, &[7]).is_none(), "not the combination that failed");
578}
579
580#[test]
581fn an_entry_whose_branch_is_gone_leaves_instead_of_blocking_those_behind() {
582 let db = database();
583 db.execute("INSERT INTO pulls (id, repo_id, number, title, agent, runtime, author_id, author_name, status, created_at, updated_at)
584 VALUES ('later-pull', 'repo', 2, 'Next', 'codex', 'external', 'user', 'user', 'open', 'now', 'now')", []).unwrap();
585 db.execute("INSERT INTO queue_entries (id, state, repo_id, pull_id, number, enqueued_by, created_at, attempt_id, combined_commit)
586 VALUES ('later', 'passed', 'repo', 'later-pull', 2, '{}', 'then', 'a2', 'tested2')", []).unwrap();
587 // 'entry' is waiting at the front and can never be built.
588 assert!(!exists(&db, BEGIN_LANDING, params!["later", "tested2", "a2"]));
589 assert!(exists(&db, LEAVE, params!["removed", super::BRANCH_GONE, "now", "entry"]));
590 assert_eq!(
591 db.query_row("SELECT error FROM queue_entries WHERE id = 'entry'", [], |row| row.get::<_, String>(0)).unwrap(),
592 "Its branch no longer exists."
593 );
594 assert!(exists(&db, BEGIN_LANDING, params!["later", "tested2", "a2"]));
595 assert_eq!(
596 super::gone_note(super::BRANCH_GONE),
597 "its branch no longer exists, so it cannot be tested or landed. Push the branch again and merge to rejoin."
598 );
599}
600
601#[test]
602fn an_entry_keeps_how_it_lands_and_one_queued_before_methods_merges() {
603 let db = database();
604 db.execute("DELETE FROM queue_entries", []).unwrap();
605 assert!(exists(&db, ENQUEUE, params!["first", "repo", "pull", 1, "{}", 0, "later", 0, "squash", "Ship it", "All of it"]));
606 let (method, title, body): (String, String, String) = db
607 .query_row("SELECT merge_method, commit_title, commit_message FROM queue_entries", [], |row| {
608 Ok((row.get(0)?, row.get(1)?, row.get(2)?))
609 })
610 .unwrap();
611 assert_eq!((method.as_str(), title.as_str(), body.as_str()), ("squash", "Ship it", "All of it"));
612 let row = |method: serde_json::Value| -> super::EntryRow {
613 serde_json::from_value(serde_json::json!({
614 "id": "e", "repo_id": "r", "pull_id": "p", "number": 1, "state": "waiting", "enqueued_by": "{}",
615 "keep_issue_open": 0, "head_commit": null, "base_commit": null, "ahead": null, "combined_commit": null,
616 "results": null, "error": null, "tested_at": null, "created_at": "now", "finished_at": null,
617 "merge_method": method,
618 }))
619 .unwrap()
620 };
621 use g1t_contracts::rules::MergeMethod;
622 assert_eq!(row("rebase".into()).method(), MergeMethod::Rebase);
623 assert_eq!(row("fast_forward".into()).method(), MergeMethod::FastForward);
624 // Its states were built with merge commits then.
625 assert_eq!(row(serde_json::Value::Null).method(), MergeMethod::Merge);
626}
627
628#[test]
629fn a_clash_names_the_nearest_entry_ahead_that_changed_the_same_file() {
630 let item = |number: u32| g1t_contracts::work::QueueStackItem {
631 number,
632 title: String::new(),
633 source: g1t_contracts::repos::RepoPath { namespace: "n".into(), name: "r".into() },
634 branch: "b".into(),
635 commit: "c".into(),
636 method: g1t_contracts::rules::MergeMethod::Squash,
637 message: None,
638 author: None,
639 };
640 let ahead = vec![
641 (item(1), vec!["a.rs".to_owned(), "b.rs".to_owned()]),
642 (item(2), vec!["b.rs".to_owned()]),
643 (item(3), vec!["c.rs".to_owned()]),
644 ];
645 assert_eq!(super::clashed_with(&ahead, &["b.rs".to_owned()]), Some(2));
646 assert_eq!(super::clashed_with(&ahead, &["a.rs".to_owned()]), Some(1));
647 // With the default branch itself.
648 assert_eq!(super::clashed_with(&ahead, &["d.rs".to_owned()]), None);
649}