Only allow one unit test function read a global variable in a certain time?

Hi, I've met a strange lock issue, the scenario is a little bit complicated, let me explain:

I need to write some unit tests, in each unit test, I need to prepare some mock data for a global variable A, and keep it unchanged during the whole test function. To keep the global variable not changed by other test cases, I introduced another global variable LOCK to try to lock it inside a test case:

// common.rs

pub struct Data {
  data: String,
}

static A: Mutex<Option<Data>> = Mutex::new();
static LOCK: Mutex<()> = Mutex::new(());

pub fn acquire() -> MutexGuard {
  let lock_guard = LOCK.lock();
  
  // Create a mock "A" for test case here.
  let a = A.lock();
  a.data = ... // mock data here
  a.unlock();

  // return lock guard
  lock_guard
}

// tests.rs

fn test1() {
  let lock_guard = acquire();

  // start running test logic

  // Lock "A", clone the value, and release it
  for i in 0..100 {
    let data = A.lock().data.clone();
    // verify data logic
    ...
  }

  // lock_guard automatically release at end, so let other test cases run
}

// Same with test1, except the verification logic is different
fn test2() {
  let lock_guard = acquire();

  // start running test logic

  // Lock "A", clone the value, and release it
  for i in 0..100 {
    let data = A.lock().data.clone();
    // verify data logic
    ...
  }

  // lock_guard automatically release at end, so let other test cases run
}

But when running multiple test cases in GitHub Actions, there are still some test cases failed. I guess the global variable A is actually not locked by LOCK, what should I do?

Your description sounds like a LazyLock would be better suited here than two mutexes.

Sorry for the misleading description, I just updated my question, can you take a look (again)?

I still don't understand why you need two mutexes. From your snippet I don't see why LOCK is needed. I.e. if A really does have to be global[1], I'd lock A once per test and remove LOCK, instead of locking LOCK for the entirety of a test while locking A multiple times while we have LOCK locked. No idea if this will fix your CI though. I don't know if a synchronisation mishap is actually the cause of it failing.


  1. From previous versions of your question I gathered that you create a config file in a temp directory and populate A with it. Why do we need to synchronise tests on this? Can't every test have their own version A? â†Šī¸Ž

Oh, the example code is still too simple, let me make it more complicated:

In each test case, after A data is initialized (prepared), it will be read multiple times during the test case, and I need to make sure A is not changed during a whole test case:

  // Lock "A", clone the value, and release it
  for i in 0..100 {
    let data = A.lock().data.clone();
    // verify data logic
    ...
  }

When cargo test runs, two test functions could be running parallel, so the global A data change be changed by different test cases.

You can read A multiple times through a single guard. You don't need to lock the mutex for each read:

  let data = A.lock();
  for i in 0..100 {
    let data = data.clone();
    // verify data logic
    ...
  }
2 Likes

Yes, you are right..... Thanks for your reply!
I think I need to try to provide a best minimal re-producible example that can describe my issue before further discussion :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.