Skip to content

Commit 8b1acc2

Browse files
authored
Check predictability of names
The README to the exercise states > The names must be random: they should not follow a predictable sequence. Yet there are accepted [solutions](https://exercism.org/tracks/rust/exercises/robot-name/solutions/boso) that violate the requirement. *) The linked solution can unfortunately not be commented on: > Comments have been disabled This new test makes sure that the most obvious kind of predictability - and probably the only one naturally used by anyone - makes tests fail. The test cannot be run concurrently with other tests, for obvious reasons, hence the not very elegant solution using a global RwLock, which every other tests has to `read()`, in order for the new test to run exclusively.
1 parent 19aa201 commit 8b1acc2

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

exercises/practice/robot-name/tests/robot_name.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
use robot_name as robot;
2+
use std::sync::{Once, RwLock};
3+
4+
static INIT: Once = Once::new();
5+
static mut PREDICTABLE_SEQ_TEST_LOCK: *const RwLock<()> = std::ptr::null();
6+
7+
/// All tests other than names_should_not_follow_predictable_sequence need to take a read lock,
8+
/// so that they will not run concurrently with names_should_not_follow_predictable_sequence, who
9+
/// takes the write lock
10+
fn predictable_seq_test_lock() -> &'static RwLock<()> {
11+
INIT.call_once(|| {
12+
let boxed = Box::new(RwLock::new(()));
13+
unsafe {
14+
PREDICTABLE_SEQ_TEST_LOCK = Box::into_raw(boxed);
15+
}
16+
});
17+
18+
unsafe { &*PREDICTABLE_SEQ_TEST_LOCK }
19+
}
220

321
fn assert_name_matches_pattern(n: &str) {
422
assert!(n.len() == 5, "name is exactly 5 characters long");
@@ -14,13 +32,15 @@ fn assert_name_matches_pattern(n: &str) {
1432

1533
#[test]
1634
fn name_should_match_expected_pattern() {
35+
let _guard = predictable_seq_test_lock().read();
1736
let r = robot::Robot::new();
1837
assert_name_matches_pattern(r.name());
1938
}
2039

2140
#[test]
2241
#[ignore]
2342
fn different_robots_have_different_names() {
43+
let _guard = predictable_seq_test_lock().read();
2444
let r1 = robot::Robot::new();
2545
let r2 = robot::Robot::new();
2646
assert_ne!(r1.name(), r2.name(), "Robot names should be different");
@@ -29,6 +49,7 @@ fn different_robots_have_different_names() {
2949
#[test]
3050
#[ignore]
3151
fn many_different_robots_have_different_names() {
52+
let _guard = predictable_seq_test_lock().read();
3253
use std::collections::HashSet;
3354

3455
// In 3,529 random robot names, there is ~99.99% chance of a name collision
@@ -42,6 +63,7 @@ fn many_different_robots_have_different_names() {
4263
#[test]
4364
#[ignore]
4465
fn new_name_should_match_expected_pattern() {
66+
let _guard = predictable_seq_test_lock().read();
4567
let mut r = robot::Robot::new();
4668
assert_name_matches_pattern(r.name());
4769
r.reset_name();
@@ -51,9 +73,38 @@ fn new_name_should_match_expected_pattern() {
5173
#[test]
5274
#[ignore]
5375
fn new_name_is_different_from_old_name() {
76+
let _guard = predictable_seq_test_lock().read();
5477
let mut r = robot::Robot::new();
5578
let n1 = r.name().to_string();
5679
r.reset_name();
5780
let n2 = r.name().to_string();
5881
assert_ne!(n1, n2, "Robot name should change when reset");
5982
}
83+
84+
fn name_to_num(name: &str) -> u32 {
85+
let mut chars = name.chars();
86+
((chars.next().unwrap() as u32) - b'A' as u32) * 26 * 10 * 10 * 10
87+
+ ((chars.next().unwrap() as u32) - b'A' as u32) * 10 * 10 * 10
88+
+ ((chars.next().unwrap() as u32) - b'0' as u32) * 10 * 10
89+
+ ((chars.next().unwrap() as u32) - b'0' as u32) * 10
90+
+ ((chars.next().unwrap() as u32) - b'0' as u32)
91+
}
92+
93+
#[test]
94+
#[ignore]
95+
fn names_should_not_follow_predictable_sequence() {
96+
// needs to run exclusively, otherwise other tests may mess up predictability detection
97+
let _guard = predictable_seq_test_lock().write();
98+
let nums = (0..10)
99+
.into_iter()
100+
.map(|_| name_to_num(robot::Robot::new().name()))
101+
.collect::<Vec<_>>();
102+
103+
// just checking the easiest kind of predictable sequences here: the difference between
104+
// numerical representation of consecutive names is always the same
105+
let d = nums[1] - nums[0];
106+
assert!(
107+
!nums.windows(2).all(|w| w[1] - w[0] == d),
108+
"Name sequence is very predictable: The difference between the numerical representation of names is {d}"
109+
)
110+
}

0 commit comments

Comments
 (0)