Repository: kellpossible/cargo-i18n (language: rust)

Issue:
Intermittent failure of fallback_ru_to_en unit test
Sometimes when running the unit tests in `i18n-embed/tests/loader.rs` I get a failure:

```
> Executing task: cargo test --package i18n-embed --test loader --all-features -- gettext --nocapture <

    Finished test [unoptimized + debuginfo] target(s) in 0.07s
     Running target/debug/deps/loader-a834f4ae71a337bd

running 2 tests
test gettext::only_en ... ok
thread 'gettext::fallback_ru_to_en' panicked at 'assertion failed: `(left == right)`

Diff < left / right > :
<"только ру"
>"only ru"

', i18n-embed/tests/loader.rs:135:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/fe982319aa0aa5bbfc2795791a753832292bd2ba/library/std/src/panicking.rs:493:5
   1: std::panicking::begin_panic_fmt
             at /rustc/fe982319aa0aa5bbfc2795791a753832292bd2ba/library/std/src/panicking.rs:435:5
   2: loader::gettext::fallback_ru_to_en
             at ./tests/loader.rs:135:9
   3: loader::gettext::fallback_ru_to_en::{{closure}}
             at ./tests/loader.rs:127:5
   4: core::ops::function::FnOnce::call_once
             at /home/luke/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
   5: core::ops::function::FnOnce::call_once
             at /rustc/fe982319aa0aa5bbfc2795791a753832292bd2ba/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
test gettext::fallback_ru_to_en ... FAILED

failures:

failures:
    gettext::fallback_ru_to_en
```


Discussion / hints:
An example of this same failure https://github.com/kellpossible/cargo-i18n/runs/1831562058?check_suite_focus=true
`cargo test` runs the tests in threads. The `gettext` tests however are not thread safe as they use the shared `LOADER` which is initialized inside the `lazy_static` block. It is just a matter of (bad) luck to end up with both tests executing concurrently. `GetTextLanguageLoader::load_langauges` does set the `current_language` which causes the tests to fail in a random fashion.

`gettext::only_en` and `gettext::fallback_ru_to_en` are easy to fix by moving the `loader` into the test functions. The code base is full of `lazy_static` though and there are more tests that fail randomly for the same reason.

The easiest way to fix this is running the tests single threaded:

```sh
cargo test --workspace --all-features -- --test-threads 1
```

https://doc.rust-lang.org/book/ch11-02-running-tests.html#running-tests-in-parallel-or-consecutively

A better way would be to make fluent thread safe by putting the current language in a thread local object instead. With that change it would no longer be possible to switch the current language for the entire running program though. That's why I think that choice should be made by the user instead. I was thinking about a `CurrentLanguage` trait which could be implemented by `ApplicationCurrentLanguage` and `ThreadCurrentLanguage` allowing for swapping out the current language implementation at loader creation time.

I guess the easiest fix (for now) is to add the `--test-threads 1` to the CI pipeline and call it the day.

Fix patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 9d7217c..88a0c10 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -19,7 +19,7 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --all --all-features
+          args: --all --all-features -- --test-threads 1
   
   fmt:
     name: Rustfmt
@@ -52,4 +52,4 @@ jobs:
       # all the time.
       - uses: actions-rs/clippy@master
         with:
-          args: --all-features
\ No newline at end of file
+          args: --all-features


Test patch:
diff --git a/i18n-embed/tests/loader.rs b/i18n-embed/tests/loader.rs
index b86475b..4b90aea 100644
--- a/i18n-embed/tests/loader.rs
+++ b/i18n-embed/tests/loader.rs
@@ -209,21 +209,19 @@ mod gettext {
     #[folder = "i18n/mo"]
     struct Localizations;
 
-    lazy_static::lazy_static! {
-        static ref LOADER: GettextLanguageLoader = GettextLanguageLoader::new("i18n_embed", "en".parse().unwrap());
-    }
-
     #[test]
     fn only_en() {
         setup();
 
+        let loader = GettextLanguageLoader::new("i18n_embed", "en".parse().unwrap());
+
         let ru: LanguageIdentifier = "ru".parse().unwrap();
         let en: LanguageIdentifier = "en".parse().unwrap();
 
-        LOADER.load_languages(&Localizations, &[&ru]).unwrap();
+        loader.load_languages(&Localizations, &[&ru]).unwrap();
 
         // It should replace the ru with en
-        LOADER.load_languages(&Localizations, &[&en]).unwrap();
+        loader.load_languages(&Localizations, &[&en]).unwrap();
 
         pretty_assertions::assert_eq!("only en", tr("only en"));
         pretty_assertions::assert_eq!("only ru", tr("only ru"));
@@ -233,10 +231,12 @@ mod gettext {
     fn fallback_ru_to_en() {
         setup();
 
+        let loader = GettextLanguageLoader::new("i18n_embed", "en".parse().unwrap());
+
         let ru: LanguageIdentifier = "ru".parse().unwrap();
 
         assert!(Localizations::get("ru/i18n_embed.mo").is_some());
-        LOADER.load_languages(&Localizations, &[&ru]).unwrap();
+        loader.load_languages(&Localizations, &[&ru]).unwrap();
 
         pretty_assertions::assert_eq!("только ру", tr("only ru"));
         pretty_assertions::assert_eq!("only en", tr("only en"));