##// END OF EJS Templates
rust-discovery: use while loop instead of match + break...
Yuya Nishihara -
r43009:88d6a6f7 default draft
parent child Browse files
Show More
@@ -1,701 +1,695 b''
1 1 // discovery.rs
2 2 //
3 3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Discovery operations
9 9 //!
10 10 //! This is a Rust counterpart to the `partialdiscovery` class of
11 11 //! `mercurial.setdiscovery`
12 12
13 13 use super::{Graph, GraphError, Revision, NULL_REVISION};
14 14 use crate::ancestors::MissingAncestors;
15 15 use crate::dagops;
16 16 use rand::seq::SliceRandom;
17 17 use rand::{thread_rng, RngCore, SeedableRng};
18 18 use std::cmp::{max, min};
19 19 use std::collections::{HashMap, HashSet, VecDeque};
20 20
21 21 type Rng = rand_pcg::Pcg32;
22 22
23 23 pub struct PartialDiscovery<G: Graph + Clone> {
24 24 target_heads: Option<Vec<Revision>>,
25 25 graph: G, // plays the role of self._repo
26 26 common: MissingAncestors<G>,
27 27 undecided: Option<HashSet<Revision>>,
28 28 children_cache: Option<HashMap<Revision, Vec<Revision>>>,
29 29 missing: HashSet<Revision>,
30 30 rng: Rng,
31 31 respect_size: bool,
32 32 randomize: bool,
33 33 }
34 34
35 35 pub struct DiscoveryStats {
36 36 pub undecided: Option<usize>,
37 37 }
38 38
39 39 /// Update an existing sample to match the expected size
40 40 ///
41 41 /// The sample is updated with revisions exponentially distant from each
42 42 /// element of `heads`.
43 43 ///
44 44 /// If a target size is specified, the sampling will stop once this size is
45 45 /// reached. Otherwise sampling will happen until roots of the <revs> set are
46 46 /// reached.
47 47 ///
48 48 /// - `revs`: set of revs we want to discover (if None, `assume` the whole dag
49 49 /// represented by `parentfn`
50 50 /// - `heads`: set of DAG head revs
51 51 /// - `sample`: a sample to update
52 52 /// - `parentfn`: a callable to resolve parents for a revision
53 53 /// - `quicksamplesize`: optional target size of the sample
54 54 fn update_sample<I>(
55 55 revs: Option<&HashSet<Revision>>,
56 56 heads: impl IntoIterator<Item = Revision>,
57 57 sample: &mut HashSet<Revision>,
58 58 parentsfn: impl Fn(Revision) -> Result<I, GraphError>,
59 59 quicksamplesize: Option<usize>,
60 60 ) -> Result<(), GraphError>
61 61 where
62 62 I: Iterator<Item = Revision>,
63 63 {
64 64 let mut distances: HashMap<Revision, u32> = HashMap::new();
65 65 let mut visit: VecDeque<Revision> = heads.into_iter().collect();
66 66 let mut factor: u32 = 1;
67 67 let mut seen: HashSet<Revision> = HashSet::new();
68 loop {
69 let current = match visit.pop_front() {
70 None => {
71 break;
72 }
73 Some(r) => r,
74 };
68 while let Some(current) = visit.pop_front() {
75 69 if !seen.insert(current) {
76 70 continue;
77 71 }
78 72
79 73 let d = *distances.entry(current).or_insert(1);
80 74 if d > factor {
81 75 factor *= 2;
82 76 }
83 77 if d == factor {
84 78 sample.insert(current);
85 79 if let Some(sz) = quicksamplesize {
86 80 if sample.len() >= sz {
87 81 return Ok(());
88 82 }
89 83 }
90 84 }
91 85 for p in parentsfn(current)? {
92 86 if let Some(revs) = revs {
93 87 if !revs.contains(&p) {
94 88 continue;
95 89 }
96 90 }
97 91 distances.entry(p).or_insert(d + 1);
98 92 visit.push_back(p);
99 93 }
100 94 }
101 95 Ok(())
102 96 }
103 97
104 98 struct ParentsIterator {
105 99 parents: [Revision; 2],
106 100 cur: usize,
107 101 }
108 102
109 103 impl ParentsIterator {
110 104 fn graph_parents(
111 105 graph: &impl Graph,
112 106 r: Revision,
113 107 ) -> Result<ParentsIterator, GraphError> {
114 108 Ok(ParentsIterator {
115 109 parents: graph.parents(r)?,
116 110 cur: 0,
117 111 })
118 112 }
119 113 }
120 114
121 115 impl Iterator for ParentsIterator {
122 116 type Item = Revision;
123 117
124 118 fn next(&mut self) -> Option<Revision> {
125 119 if self.cur > 1 {
126 120 return None;
127 121 }
128 122 let rev = self.parents[self.cur];
129 123 self.cur += 1;
130 124 if rev == NULL_REVISION {
131 125 return self.next();
132 126 }
133 127 Some(rev)
134 128 }
135 129 }
136 130
137 131 impl<G: Graph + Clone> PartialDiscovery<G> {
138 132 /// Create a PartialDiscovery object, with the intent
139 133 /// of comparing our `::<target_heads>` revset to the contents of another
140 134 /// repo.
141 135 ///
142 136 /// For now `target_heads` is passed as a vector, and will be used
143 137 /// at the first call to `ensure_undecided()`.
144 138 ///
145 139 /// If we want to make the signature more flexible,
146 140 /// we'll have to make it a type argument of `PartialDiscovery` or a trait
147 141 /// object since we'll keep it in the meanwhile
148 142 ///
149 143 /// The `respect_size` boolean controls how the sampling methods
150 144 /// will interpret the size argument requested by the caller. If it's
151 145 /// `false`, they are allowed to produce a sample whose size is more
152 146 /// appropriate to the situation (typically bigger).
153 147 ///
154 148 /// The `randomize` boolean affects sampling, and specifically how
155 149 /// limiting or last-minute expanding is been done:
156 150 ///
157 151 /// If `true`, both will perform random picking from `self.undecided`.
158 152 /// This is currently the best for actual discoveries.
159 153 ///
160 154 /// If `false`, a reproductible picking strategy is performed. This is
161 155 /// useful for integration tests.
162 156 pub fn new(
163 157 graph: G,
164 158 target_heads: Vec<Revision>,
165 159 respect_size: bool,
166 160 randomize: bool,
167 161 ) -> Self {
168 162 let mut seed: [u8; 16] = [0; 16];
169 163 if randomize {
170 164 thread_rng().fill_bytes(&mut seed);
171 165 }
172 166 Self::new_with_seed(graph, target_heads, seed, respect_size, randomize)
173 167 }
174 168
175 169 pub fn new_with_seed(
176 170 graph: G,
177 171 target_heads: Vec<Revision>,
178 172 seed: [u8; 16],
179 173 respect_size: bool,
180 174 randomize: bool,
181 175 ) -> Self {
182 176 PartialDiscovery {
183 177 undecided: None,
184 178 children_cache: None,
185 179 target_heads: Some(target_heads),
186 180 graph: graph.clone(),
187 181 common: MissingAncestors::new(graph, vec![]),
188 182 missing: HashSet::new(),
189 183 rng: Rng::from_seed(seed),
190 184 respect_size: respect_size,
191 185 randomize: randomize,
192 186 }
193 187 }
194 188
195 189 /// Extract at most `size` random elements from sample and return them
196 190 /// as a vector
197 191 fn limit_sample(
198 192 &mut self,
199 193 mut sample: Vec<Revision>,
200 194 size: usize,
201 195 ) -> Vec<Revision> {
202 196 if !self.randomize {
203 197 sample.sort();
204 198 sample.truncate(size);
205 199 return sample;
206 200 }
207 201 let sample_len = sample.len();
208 202 if sample_len <= size {
209 203 return sample;
210 204 }
211 205 let rng = &mut self.rng;
212 206 let dropped_size = sample_len - size;
213 207 let limited_slice = if size < dropped_size {
214 208 sample.partial_shuffle(rng, size).0
215 209 } else {
216 210 sample.partial_shuffle(rng, dropped_size).1
217 211 };
218 212 limited_slice.to_owned()
219 213 }
220 214
221 215 /// Register revisions known as being common
222 216 pub fn add_common_revisions(
223 217 &mut self,
224 218 common: impl IntoIterator<Item = Revision>,
225 219 ) -> Result<(), GraphError> {
226 220 let before_len = self.common.get_bases().len();
227 221 self.common.add_bases(common);
228 222 if self.common.get_bases().len() == before_len {
229 223 return Ok(());
230 224 }
231 225 if let Some(ref mut undecided) = self.undecided {
232 226 self.common.remove_ancestors_from(undecided)?;
233 227 }
234 228 Ok(())
235 229 }
236 230
237 231 /// Register revisions known as being missing
238 232 ///
239 233 /// # Performance note
240 234 ///
241 235 /// Except in the most trivial case, the first call of this method has
242 236 /// the side effect of computing `self.undecided` set for the first time,
243 237 /// and the related caches it might need for efficiency of its internal
244 238 /// computation. This is typically faster if more information is
245 239 /// available in `self.common`. Therefore, for good performance, the
246 240 /// caller should avoid calling this too early.
247 241 pub fn add_missing_revisions(
248 242 &mut self,
249 243 missing: impl IntoIterator<Item = Revision>,
250 244 ) -> Result<(), GraphError> {
251 245 let mut tovisit: VecDeque<Revision> = missing.into_iter().collect();
252 246 if tovisit.is_empty() {
253 247 return Ok(());
254 248 }
255 249 self.ensure_children_cache()?;
256 250 self.ensure_undecided()?; // for safety of possible future refactors
257 251 let children = self.children_cache.as_ref().unwrap();
258 252 let mut seen: HashSet<Revision> = HashSet::new();
259 253 let undecided_mut = self.undecided.as_mut().unwrap();
260 254 while let Some(rev) = tovisit.pop_front() {
261 255 if !self.missing.insert(rev) {
262 256 // either it's known to be missing from a previous
263 257 // invocation, and there's no need to iterate on its
264 258 // children (we now they are all missing)
265 259 // or it's from a previous iteration of this loop
266 260 // and its children have already been queued
267 261 continue;
268 262 }
269 263 undecided_mut.remove(&rev);
270 264 match children.get(&rev) {
271 265 None => {
272 266 continue;
273 267 }
274 268 Some(this_children) => {
275 269 for child in this_children.iter().cloned() {
276 270 if seen.insert(child) {
277 271 tovisit.push_back(child);
278 272 }
279 273 }
280 274 }
281 275 }
282 276 }
283 277 Ok(())
284 278 }
285 279
286 280 /// Do we have any information about the peer?
287 281 pub fn has_info(&self) -> bool {
288 282 self.common.has_bases()
289 283 }
290 284
291 285 /// Did we acquire full knowledge of our Revisions that the peer has?
292 286 pub fn is_complete(&self) -> bool {
293 287 self.undecided.as_ref().map_or(false, |s| s.is_empty())
294 288 }
295 289
296 290 /// Return the heads of the currently known common set of revisions.
297 291 ///
298 292 /// If the discovery process is not complete (see `is_complete()`), the
299 293 /// caller must be aware that this is an intermediate state.
300 294 ///
301 295 /// On the other hand, if it is complete, then this is currently
302 296 /// the only way to retrieve the end results of the discovery process.
303 297 ///
304 298 /// We may introduce in the future an `into_common_heads` call that
305 299 /// would be more appropriate for normal Rust callers, dropping `self`
306 300 /// if it is complete.
307 301 pub fn common_heads(&self) -> Result<HashSet<Revision>, GraphError> {
308 302 self.common.bases_heads()
309 303 }
310 304
311 305 /// Force first computation of `self.undecided`
312 306 ///
313 307 /// After this, `self.undecided.as_ref()` and `.as_mut()` can be
314 308 /// unwrapped to get workable immutable or mutable references without
315 309 /// any panic.
316 310 ///
317 311 /// This is an imperative call instead of an access with added lazyness
318 312 /// to reduce easily the scope of mutable borrow for the caller,
319 313 /// compared to undecided(&'a mut self) -> &'a… that would keep it
320 314 /// as long as the resulting immutable one.
321 315 fn ensure_undecided(&mut self) -> Result<(), GraphError> {
322 316 if self.undecided.is_some() {
323 317 return Ok(());
324 318 }
325 319 let tgt = self.target_heads.take().unwrap();
326 320 self.undecided =
327 321 Some(self.common.missing_ancestors(tgt)?.into_iter().collect());
328 322 Ok(())
329 323 }
330 324
331 325 fn ensure_children_cache(&mut self) -> Result<(), GraphError> {
332 326 if self.children_cache.is_some() {
333 327 return Ok(());
334 328 }
335 329 self.ensure_undecided()?;
336 330
337 331 let mut children: HashMap<Revision, Vec<Revision>> = HashMap::new();
338 332 for &rev in self.undecided.as_ref().unwrap() {
339 333 for p in ParentsIterator::graph_parents(&self.graph, rev)? {
340 334 children.entry(p).or_insert_with(|| Vec::new()).push(rev);
341 335 }
342 336 }
343 337 self.children_cache = Some(children);
344 338 Ok(())
345 339 }
346 340
347 341 /// Provide statistics about the current state of the discovery process
348 342 pub fn stats(&self) -> DiscoveryStats {
349 343 DiscoveryStats {
350 344 undecided: self.undecided.as_ref().map(|s| s.len()),
351 345 }
352 346 }
353 347
354 348 pub fn take_quick_sample(
355 349 &mut self,
356 350 headrevs: impl IntoIterator<Item = Revision>,
357 351 size: usize,
358 352 ) -> Result<Vec<Revision>, GraphError> {
359 353 self.ensure_undecided()?;
360 354 let mut sample = {
361 355 let undecided = self.undecided.as_ref().unwrap();
362 356 if undecided.len() <= size {
363 357 return Ok(undecided.iter().cloned().collect());
364 358 }
365 359 dagops::heads(&self.graph, undecided.iter())?
366 360 };
367 361 if sample.len() >= size {
368 362 return Ok(self.limit_sample(sample.into_iter().collect(), size));
369 363 }
370 364 update_sample(
371 365 None,
372 366 headrevs,
373 367 &mut sample,
374 368 |r| ParentsIterator::graph_parents(&self.graph, r),
375 369 Some(size),
376 370 )?;
377 371 Ok(sample.into_iter().collect())
378 372 }
379 373
380 374 /// Extract a sample from `self.undecided`, going from its heads and roots.
381 375 ///
382 376 /// The `size` parameter is used to avoid useless computations if
383 377 /// it turns out to be bigger than the whole set of undecided Revisions.
384 378 ///
385 379 /// The sample is taken by using `update_sample` from the heads, then
386 380 /// from the roots, working on the reverse DAG,
387 381 /// expressed by `self.children_cache`.
388 382 ///
389 383 /// No effort is being made to complete or limit the sample to `size`
390 384 /// but this method returns another interesting size that it derives
391 385 /// from its knowledge of the structure of the various sets, leaving
392 386 /// to the caller the decision to use it or not.
393 387 fn bidirectional_sample(
394 388 &mut self,
395 389 size: usize,
396 390 ) -> Result<(HashSet<Revision>, usize), GraphError> {
397 391 self.ensure_undecided()?;
398 392 {
399 393 // we don't want to compute children_cache before this
400 394 // but doing it after extracting self.undecided takes a mutable
401 395 // ref to self while a shareable one is still active.
402 396 let undecided = self.undecided.as_ref().unwrap();
403 397 if undecided.len() <= size {
404 398 return Ok((undecided.clone(), size));
405 399 }
406 400 }
407 401
408 402 self.ensure_children_cache()?;
409 403 let revs = self.undecided.as_ref().unwrap();
410 404 let mut sample: HashSet<Revision> = revs.clone();
411 405
412 406 // it's possible that leveraging the children cache would be more
413 407 // efficient here
414 408 dagops::retain_heads(&self.graph, &mut sample)?;
415 409 let revsheads = sample.clone(); // was again heads(revs) in python
416 410
417 411 // update from heads
418 412 update_sample(
419 413 Some(revs),
420 414 revsheads.iter().cloned(),
421 415 &mut sample,
422 416 |r| ParentsIterator::graph_parents(&self.graph, r),
423 417 None,
424 418 )?;
425 419
426 420 // update from roots
427 421 let revroots: HashSet<Revision> =
428 422 dagops::roots(&self.graph, revs)?.into_iter().collect();
429 423 let prescribed_size = max(size, min(revroots.len(), revsheads.len()));
430 424
431 425 let children = self.children_cache.as_ref().unwrap();
432 426 let empty_vec: Vec<Revision> = Vec::new();
433 427 update_sample(
434 428 Some(revs),
435 429 revroots,
436 430 &mut sample,
437 431 |r| Ok(children.get(&r).unwrap_or(&empty_vec).iter().cloned()),
438 432 None,
439 433 )?;
440 434 Ok((sample, prescribed_size))
441 435 }
442 436
443 437 /// Fill up sample up to the wished size with random undecided Revisions.
444 438 ///
445 439 /// This is intended to be used as a last resort completion if the
446 440 /// regular sampling algorithm returns too few elements.
447 441 fn random_complete_sample(
448 442 &mut self,
449 443 sample: &mut Vec<Revision>,
450 444 size: usize,
451 445 ) {
452 446 let sample_len = sample.len();
453 447 if size <= sample_len {
454 448 return;
455 449 }
456 450 let take_from: Vec<Revision> = self
457 451 .undecided
458 452 .as_ref()
459 453 .unwrap()
460 454 .iter()
461 455 .filter(|&r| !sample.contains(r))
462 456 .cloned()
463 457 .collect();
464 458 sample.extend(self.limit_sample(take_from, size - sample_len));
465 459 }
466 460
467 461 pub fn take_full_sample(
468 462 &mut self,
469 463 size: usize,
470 464 ) -> Result<Vec<Revision>, GraphError> {
471 465 let (sample_set, prescribed_size) = self.bidirectional_sample(size)?;
472 466 let size = if self.respect_size {
473 467 size
474 468 } else {
475 469 prescribed_size
476 470 };
477 471 let mut sample =
478 472 self.limit_sample(sample_set.into_iter().collect(), size);
479 473 self.random_complete_sample(&mut sample, size);
480 474 Ok(sample)
481 475 }
482 476 }
483 477
484 478 #[cfg(test)]
485 479 mod tests {
486 480 use super::*;
487 481 use crate::testing::SampleGraph;
488 482
489 483 /// A PartialDiscovery as for pushing all the heads of `SampleGraph`
490 484 ///
491 485 /// To avoid actual randomness in these tests, we give it a fixed
492 486 /// random seed, but by default we'll test the random version.
493 487 fn full_disco() -> PartialDiscovery<SampleGraph> {
494 488 PartialDiscovery::new_with_seed(
495 489 SampleGraph,
496 490 vec![10, 11, 12, 13],
497 491 [0; 16],
498 492 true,
499 493 true,
500 494 )
501 495 }
502 496
503 497 /// A PartialDiscovery as for pushing the 12 head of `SampleGraph`
504 498 ///
505 499 /// To avoid actual randomness in tests, we give it a fixed random seed.
506 500 fn disco12() -> PartialDiscovery<SampleGraph> {
507 501 PartialDiscovery::new_with_seed(
508 502 SampleGraph,
509 503 vec![12],
510 504 [0; 16],
511 505 true,
512 506 true,
513 507 )
514 508 }
515 509
516 510 fn sorted_undecided(
517 511 disco: &PartialDiscovery<SampleGraph>,
518 512 ) -> Vec<Revision> {
519 513 let mut as_vec: Vec<Revision> =
520 514 disco.undecided.as_ref().unwrap().iter().cloned().collect();
521 515 as_vec.sort();
522 516 as_vec
523 517 }
524 518
525 519 fn sorted_missing(disco: &PartialDiscovery<SampleGraph>) -> Vec<Revision> {
526 520 let mut as_vec: Vec<Revision> =
527 521 disco.missing.iter().cloned().collect();
528 522 as_vec.sort();
529 523 as_vec
530 524 }
531 525
532 526 fn sorted_common_heads(
533 527 disco: &PartialDiscovery<SampleGraph>,
534 528 ) -> Result<Vec<Revision>, GraphError> {
535 529 let mut as_vec: Vec<Revision> =
536 530 disco.common_heads()?.iter().cloned().collect();
537 531 as_vec.sort();
538 532 Ok(as_vec)
539 533 }
540 534
541 535 #[test]
542 536 fn test_add_common_get_undecided() -> Result<(), GraphError> {
543 537 let mut disco = full_disco();
544 538 assert_eq!(disco.undecided, None);
545 539 assert!(!disco.has_info());
546 540 assert_eq!(disco.stats().undecided, None);
547 541
548 542 disco.add_common_revisions(vec![11, 12])?;
549 543 assert!(disco.has_info());
550 544 assert!(!disco.is_complete());
551 545 assert!(disco.missing.is_empty());
552 546
553 547 // add_common_revisions did not trigger a premature computation
554 548 // of `undecided`, let's check that and ask for them
555 549 assert_eq!(disco.undecided, None);
556 550 disco.ensure_undecided()?;
557 551 assert_eq!(sorted_undecided(&disco), vec![5, 8, 10, 13]);
558 552 assert_eq!(disco.stats().undecided, Some(4));
559 553 Ok(())
560 554 }
561 555
562 556 /// in this test, we pretend that our peer misses exactly (8+10)::
563 557 /// and we're comparing all our repo to it (as in a bare push)
564 558 #[test]
565 559 fn test_discovery() -> Result<(), GraphError> {
566 560 let mut disco = full_disco();
567 561 disco.add_common_revisions(vec![11, 12])?;
568 562 disco.add_missing_revisions(vec![8, 10])?;
569 563 assert_eq!(sorted_undecided(&disco), vec![5]);
570 564 assert_eq!(sorted_missing(&disco), vec![8, 10, 13]);
571 565 assert!(!disco.is_complete());
572 566
573 567 disco.add_common_revisions(vec![5])?;
574 568 assert_eq!(sorted_undecided(&disco), vec![]);
575 569 assert_eq!(sorted_missing(&disco), vec![8, 10, 13]);
576 570 assert!(disco.is_complete());
577 571 assert_eq!(sorted_common_heads(&disco)?, vec![5, 11, 12]);
578 572 Ok(())
579 573 }
580 574
581 575 #[test]
582 576 fn test_add_missing_early_continue() -> Result<(), GraphError> {
583 577 eprintln!("test_add_missing_early_stop");
584 578 let mut disco = full_disco();
585 579 disco.add_common_revisions(vec![13, 3, 4])?;
586 580 disco.ensure_children_cache()?;
587 581 // 12 is grand-child of 6 through 9
588 582 // passing them in this order maximizes the chances of the
589 583 // early continue to do the wrong thing
590 584 disco.add_missing_revisions(vec![6, 9, 12])?;
591 585 assert_eq!(sorted_undecided(&disco), vec![5, 7, 10, 11]);
592 586 assert_eq!(sorted_missing(&disco), vec![6, 9, 12]);
593 587 assert!(!disco.is_complete());
594 588 Ok(())
595 589 }
596 590
597 591 #[test]
598 592 fn test_limit_sample_no_need_to() {
599 593 let sample = vec![1, 2, 3, 4];
600 594 assert_eq!(full_disco().limit_sample(sample, 10), vec![1, 2, 3, 4]);
601 595 }
602 596
603 597 #[test]
604 598 fn test_limit_sample_less_than_half() {
605 599 assert_eq!(full_disco().limit_sample((1..6).collect(), 2), vec![4, 2]);
606 600 }
607 601
608 602 #[test]
609 603 fn test_limit_sample_more_than_half() {
610 604 assert_eq!(full_disco().limit_sample((1..4).collect(), 2), vec![3, 2]);
611 605 }
612 606
613 607 #[test]
614 608 fn test_limit_sample_no_random() {
615 609 let mut disco = full_disco();
616 610 disco.randomize = false;
617 611 assert_eq!(
618 612 disco.limit_sample(vec![1, 8, 13, 5, 7, 3], 4),
619 613 vec![1, 3, 5, 7]
620 614 );
621 615 }
622 616
623 617 #[test]
624 618 fn test_quick_sample_enough_undecided_heads() -> Result<(), GraphError> {
625 619 let mut disco = full_disco();
626 620 disco.undecided = Some((1..=13).collect());
627 621
628 622 let mut sample_vec = disco.take_quick_sample(vec![], 4)?;
629 623 sample_vec.sort();
630 624 assert_eq!(sample_vec, vec![10, 11, 12, 13]);
631 625 Ok(())
632 626 }
633 627
634 628 #[test]
635 629 fn test_quick_sample_climbing_from_12() -> Result<(), GraphError> {
636 630 let mut disco = disco12();
637 631 disco.ensure_undecided()?;
638 632
639 633 let mut sample_vec = disco.take_quick_sample(vec![12], 4)?;
640 634 sample_vec.sort();
641 635 // r12's only parent is r9, whose unique grand-parent through the
642 636 // diamond shape is r4. This ends there because the distance from r4
643 637 // to the root is only 3.
644 638 assert_eq!(sample_vec, vec![4, 9, 12]);
645 639 Ok(())
646 640 }
647 641
648 642 #[test]
649 643 fn test_children_cache() -> Result<(), GraphError> {
650 644 let mut disco = full_disco();
651 645 disco.ensure_children_cache()?;
652 646
653 647 let cache = disco.children_cache.unwrap();
654 648 assert_eq!(cache.get(&2).cloned(), Some(vec![4]));
655 649 assert_eq!(cache.get(&10).cloned(), None);
656 650
657 651 let mut children_4 = cache.get(&4).cloned().unwrap();
658 652 children_4.sort();
659 653 assert_eq!(children_4, vec![5, 6, 7]);
660 654
661 655 let mut children_7 = cache.get(&7).cloned().unwrap();
662 656 children_7.sort();
663 657 assert_eq!(children_7, vec![9, 11]);
664 658
665 659 Ok(())
666 660 }
667 661
668 662 #[test]
669 663 fn test_complete_sample() {
670 664 let mut disco = full_disco();
671 665 let undecided: HashSet<Revision> =
672 666 [4, 7, 9, 2, 3].iter().cloned().collect();
673 667 disco.undecided = Some(undecided);
674 668
675 669 let mut sample = vec![0];
676 670 disco.random_complete_sample(&mut sample, 3);
677 671 assert_eq!(sample.len(), 3);
678 672
679 673 let mut sample = vec![2, 4, 7];
680 674 disco.random_complete_sample(&mut sample, 1);
681 675 assert_eq!(sample.len(), 3);
682 676 }
683 677
684 678 #[test]
685 679 fn test_bidirectional_sample() -> Result<(), GraphError> {
686 680 let mut disco = full_disco();
687 681 disco.undecided = Some((0..=13).into_iter().collect());
688 682
689 683 let (sample_set, size) = disco.bidirectional_sample(7)?;
690 684 assert_eq!(size, 7);
691 685 let mut sample: Vec<Revision> = sample_set.into_iter().collect();
692 686 sample.sort();
693 687 // our DAG is a bit too small for the results to be really interesting
694 688 // at least it shows that
695 689 // - we went both ways
696 690 // - we didn't take all Revisions (6 is not in the sample)
697 691 assert_eq!(sample, vec![0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13]);
698 692 Ok(())
699 693 }
700 694
701 695 }
General Comments 0
You need to be logged in to leave comments. Login now