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