Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"0f7f7edf472da67feccf04bceba3f35d54ff1b7518ac3a8a3a812bef3a4427a8","Cargo.lock":"db4b866b83a10a644e853ea5bbbc3ff97de57454b5731f34b3878a9f2fea3b26","Cargo.toml":"12b942d2e96eb463e364610660117193a25e3aad6bb9b7e98809a6cc2708dfeb","Cargo.toml.orig":"418792e53acd36aa7b628f83ef55895bb109da863eced9e41b48c56338add1da","LICENSE":"ac7e05bd11cc1cfc3f9452c1b9986a9b1d54e180fa88e44e69caf955f95dc8a6","README.md":"f2c45c74c939294a5801bc8b6c903a74740cd3dce7523fc80b43b074fffbb4c6","src/code_lock.rs":"857ad5973cc2c71d09a83dfc61dcb8a92223b36db9d0ca8c2d9698addb3cddb6","src/file_lock.rs":"b2aecbc093c64c09dd16655658805659dbc72c668049a24c345f5391321e1af4","src/lib.rs":"028220a919afee6947bc4d3a14d4afa06a04f35177dae638be18334bdb98e707","src/parallel_code_lock.rs":"f1d412db88ba7fad2cde7eec7a18445423878797eaa6543b30b9e7b864e2251d","src/parallel_file_lock.rs":"58155838b5fc8c164fda2c330404b363bcad81c163296ee009d96ebb9d43e243","src/rwlock.rs":"216dbaf62a58a43ebcc45ae9663f107ad7a4c41eb4756753a2fbb00a6c2b2cec","src/serial_code_lock.rs":"dd42f561fed2b3c9cdc25bacb6a5781a7aa81d768146c684d1327c2ad80c73ab","src/serial_file_lock.rs":"13d338cb98e59a3434ad657c926196bc9b62474710d3418259ed25d5c27dbc7a","tests/tests.rs":"76ed1916b07416be281e407bd078c323f64c0830cc5c24898631a51a065bd8d9"},"package":"699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "6181f64de942180231fdb9098dd0894bbd9e7472"
},
"path_in_vcs": "serial_test"
}
+346
View File
@@ -0,0 +1,346 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "document-features"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
dependencies = [
"litrs",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "env_logger"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
dependencies = [
"log",
]
[[package]]
name = "fslock"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serial_test"
version = "3.5.0"
dependencies = [
"document-features",
"env_logger",
"fslock",
"futures-executor",
"futures-util",
"itertools",
"log",
"once_cell",
"parking_lot",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+120
View File
@@ -0,0 +1,120 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.68"
name = "serial_test"
version = "3.5.0"
authors = ["Tom Parker-Shemilt <palfrey@tevp.net>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Allows for the creation of serialised Rust tests"
readme = "README.md"
keywords = [
"sequential",
"testing",
"parallel",
]
categories = ["development-tools::testing"]
license = "MIT"
repository = "https://github.com/palfrey/serial_test/"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[package.metadata.cargo-all-features]
skip_optional_dependencies = true
denylist = ["docsrs"]
always_include_features = ["test_logging"]
[features]
async = [
"dep:futures-executor",
"dep:futures-util",
"serial_test_derive/async",
]
default = [
"logging",
"async",
]
docsrs = ["dep:document-features"]
file_locks = ["dep:fslock"]
logging = ["dep:log"]
test_logging = [
"logging",
"dep:env_logger",
"serial_test_derive/test_logging",
]
[lib]
name = "serial_test"
path = "src/lib.rs"
[[test]]
name = "tests"
path = "tests/tests.rs"
[dependencies.document-features]
version = "0.2"
optional = true
[dependencies.env_logger]
version = ">=0.6.1"
optional = true
default-features = false
[dependencies.fslock]
version = "0.2"
features = ["std"]
optional = true
default-features = false
[dependencies.futures-executor]
version = "^0.3"
features = ["std"]
optional = true
default-features = false
[dependencies.futures-util]
version = "^0.3"
features = ["std"]
optional = true
default-features = false
[dependencies.log]
version = ">=0.4.4"
optional = true
[dependencies.once_cell]
version = "^1.19"
features = ["std"]
default-features = false
[dependencies.parking_lot]
version = "^0.12"
default-features = false
[dependencies.serial_test_derive]
version = "~3.5.0"
[dev-dependencies.itertools]
version = ">=0.4"
features = ["use_std"]
default-features = false
+57
View File
@@ -0,0 +1,57 @@
[package]
name = "serial_test"
description = "Allows for the creation of serialised Rust tests"
license = "MIT"
version = "3.5.0"
authors = ["Tom Parker-Shemilt <palfrey@tevp.net>"]
edition = "2018"
rust-version.workspace = true
repository = "https://github.com/palfrey/serial_test/"
readme = "README.md"
categories = ["development-tools::testing"]
keywords = ["sequential", "testing", "parallel"]
[dependencies]
once_cell = {version="^1.19", features = ["std"], default-features = false}
parking_lot = {version="^0.12", default-features = false}
serial_test_derive = { version = "~3.5.0", path = "../serial_test_derive" }
fslock = { version = "0.2", optional = true, default-features = false, features = ["std"]}
document-features = { version = "0.2", optional = true }
log = { version = ">=0.4.4", optional = true }
futures-executor = { version = "^0.3", optional = true, default-features = false, features = ["std"] }
futures-util = { version = "^0.3", optional = true, default-features = false, features = ["std"] }
env_logger = {version=">=0.6.1", optional=true, default-features = false}
[dev-dependencies]
itertools = {version=">=0.4", default-features = false, features = ["use_std"]}
[features]
default = ["logging", "async"]
## Switches on debug logging
logging = ["dep:log"]
## Enables async features using `futures`
async = ["dep:futures-executor", "dep:futures-util", "serial_test_derive/async"]
## The file_locks feature unlocks the `file_serial`/`file_parallel` macros
file_locks = ["dep:fslock"]
#! ### Internal-only features
## Switches on debug with `env_logger`. Generally only needed by internal `serial_test` work.
test_logging = ["logging", "dep:env_logger", "serial_test_derive/test_logging"]
docsrs = ["dep:document-features"]
# docs.rs-specific configuration
[package.metadata.docs.rs]
all-features = true
# defines the configuration attribute `docsrs`
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.cargo-all-features]
skip_optional_dependencies = true
denylist = ["docsrs"]
always_include_features = ["test_logging"]
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2018 Tom Parker-Shemilt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+65
View File
@@ -0,0 +1,65 @@
# serial_test
[![Version](https://img.shields.io/crates/v/serial_test.svg)](https://crates.io/crates/serial_test)
[![Downloads](https://img.shields.io/crates/d/serial_test)](https://crates.io/crates/serial_test)
[![Docs](https://docs.rs/serial_test/badge.svg)](https://docs.rs/serial_test/)
[![MIT license](https://img.shields.io/crates/l/serial_test.svg)](./LICENSE)
[![Build Status](https://github.com/palfrey/serial_test/actions/workflows/ci.yml/badge.svg)](https://github.com/palfrey/serial_test/actions)
[![MSRV: 1.68.0](https://flat.badgen.net/badge/MSRV/1.68.0/purple)](https://blog.rust-lang.org/2023/03/09/Rust-1.68.0/)
`serial_test` allows for the creation of serialised Rust tests using the `serial` attribute
e.g.
```rust
use serial_test::serial;
#[test]
#[serial]
fn test_serial_one() {
// Do things
}
#[test]
#[serial]
fn test_serial_another() {
// Do things
}
#[tokio::test]
#[serial]
async fn test_serial_another() {
// Do things asynchronously
}
```
Multiple tests with the `serial` attribute are guaranteed to be executed in serial. Ordering of the tests is not guaranteed however. Other tests with the `parallel` attribute may run at the same time as each other, but not at the same time as a test with `serial`. Tests with neither attribute may run at any time and no guarantees are made about their timing! Both support optional keys for defining subsets of tests to run in serial together, see docs for more details.
For cases like doctests and integration tests where the tests are run as separate processes, we also support `file_serial`, with
similar properties but based off file locking. Note that there are no guarantees about one test with `serial` and another with
`file_serial` as they lock using different methods.
All of the attributes can also be applied at a `mod` level and will be automagically applied to all test functions in that block.
## Inner Attributes
You can apply attributes to an inner test function using `inner_attrs`. This is useful for applying attributes like `ntest::timeout` that should only affect the test body, not the mutex/lock acquisition:
```rust
#[test]
#[serial(inner_attrs = [ntest::timeout(1000)])]
fn test_with_timeout() {
// The timeout only applies to this body, not the serial lock acquisition
}
```
This can be combined with keys: `#[serial(my_key, inner_attrs = [timeout(1000)])]`
## Usage
The minimum supported Rust version here is 1.68.0. Note this is minimum _supported_, as it may well compile with lower versions, but they're not supported at all. Upgrades to this will require at a major version bump. 1.x supports 1.51 if you need a lower version than that.
Add to your Cargo.toml
```toml
[dev-dependencies]
serial_test = "*"
```
plus `use serial_test::serial;` in your imports section.
You can then either add `#[serial]` or `#[serial(some_key)]` to tests as required.
+226
View File
@@ -0,0 +1,226 @@
use crate::rwlock::{Locks, MutexGuardWrapper};
use once_cell::sync::OnceCell;
use std::{
collections::HashMap,
sync::{atomic::AtomicU32, Mutex},
};
pub(crate) struct ValueRef(UniqueReentrantMutex);
impl ValueRef {
pub fn get(&self) -> &UniqueReentrantMutex {
&self.0
}
}
pub(crate) struct LockMap {
inner: Mutex<HashMap<String, UniqueReentrantMutex>>,
}
impl LockMap {
fn new() -> Self {
LockMap {
inner: Mutex::new(HashMap::new()),
}
}
pub fn get(&self, key: &str) -> Option<ValueRef> {
self.inner.lock().unwrap().get(key).cloned().map(ValueRef)
}
fn get_or_insert(
&self,
key: &str,
f: impl FnOnce() -> UniqueReentrantMutex,
) -> UniqueReentrantMutex {
let mut map = self.inner.lock().unwrap();
map.entry(key.to_owned()).or_insert_with(f).clone()
}
}
#[derive(Clone)]
pub(crate) struct UniqueReentrantMutex {
locks: Locks,
// Only actually used for tests
#[allow(dead_code)]
pub(crate) id: u32,
}
impl UniqueReentrantMutex {
pub(crate) fn lock(&self) -> MutexGuardWrapper<'_> {
self.locks.serial()
}
pub(crate) fn start_parallel(&self) {
self.locks.start_parallel();
}
pub(crate) fn end_parallel(&self) {
self.locks.end_parallel();
}
#[cfg(test)]
pub fn parallel_count(&self) -> u32 {
self.locks.parallel_count()
}
#[cfg(test)]
pub fn is_locked(&self) -> bool {
self.locks.is_locked()
}
pub fn is_locked_by_current_thread(&self) -> bool {
self.locks.is_locked_by_current_thread()
}
}
#[inline]
pub(crate) fn global_locks() -> &'static LockMap {
#[cfg(feature = "test_logging")]
let _ = env_logger::builder().try_init();
static LOCKS: OnceCell<LockMap> = OnceCell::new();
LOCKS.get_or_init(LockMap::new)
}
/// Check if the current thread is holding a serial lock
///
/// Can be used to assert that a piece of code can only be called
/// from a test marked `#[serial]`.
///
/// Example, with `#[serial]`:
///
/// ```no_run
/// use serial_test::{is_locked_serially, serial};
///
/// fn do_something_in_need_of_serialization() {
/// assert!(is_locked_serially(None));
///
/// // ...
/// }
///
/// #[test]
/// # fn unused() {}
/// #[serial]
/// fn main() {
/// do_something_in_need_of_serialization();
/// }
/// ```
///
/// Example, missing `#[serial]`:
///
/// ```should_panic,no_run
/// use serial_test::{is_locked_serially, serial};
///
/// #[test]
/// # fn unused() {}
/// // #[serial] // <-- missing
/// fn main() {
/// assert!(is_locked_serially(None));
/// }
/// ```
///
/// Example, `#[test(some_key)]`:
///
/// ```no_run
/// use serial_test::{is_locked_serially, serial};
///
/// #[test]
/// # fn unused() {}
/// #[serial(some_key)]
/// fn main() {
/// assert!(is_locked_serially(Some("some_key")));
/// assert!(!is_locked_serially(None));
/// }
/// ```
pub fn is_locked_serially(name: Option<&str>) -> bool {
global_locks()
.get(name.unwrap_or_default())
.map(|lock| lock.get().is_locked_by_current_thread())
.unwrap_or_default()
}
static MUTEX_ID: AtomicU32 = AtomicU32::new(1);
impl UniqueReentrantMutex {
fn new_mutex(name: &str) -> Self {
Self {
locks: Locks::new(name),
id: MUTEX_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
}
}
}
pub(crate) fn check_new_key(name: &str) {
global_locks().get_or_insert(name, || UniqueReentrantMutex::new_mutex(name));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{local_parallel_core, local_serial_core};
#[test]
fn assert_serially_locked_without_name() {
local_serial_core(vec![""], None, || {
assert!(is_locked_serially(None));
assert!(!is_locked_serially(Some(
"no_such_name_assert_serially_locked_without_name"
)));
});
}
#[test]
fn assert_serially_locked_with_multiple_names() {
const NAME1: &str = "assert_serially_locked_with_multiple_names-NAME1";
const NAME2: &str = "assert_serially_locked_with_multiple_names-NAME2";
local_serial_core(vec![NAME1, NAME2], None, || {
assert!(is_locked_serially(Some(NAME1)));
assert!(is_locked_serially(Some(NAME2)));
assert!(!is_locked_serially(Some(
"no_such_name_assert_serially_locked_with_multiple_names"
)));
});
}
#[test]
fn assert_serially_locked_when_actually_locked_parallel() {
const NAME1: &str = "assert_serially_locked_when_actually_locked_parallel-NAME1";
const NAME2: &str = "assert_serially_locked_when_actually_locked_parallel-NAME2";
local_parallel_core(vec![NAME1, NAME2], None, || {
assert!(!is_locked_serially(Some(NAME1)));
assert!(!is_locked_serially(Some(NAME2)));
assert!(!is_locked_serially(Some(
"no_such_name_assert_serially_locked_when_actually_locked_parallel"
)));
});
}
#[test]
fn assert_serially_locked_outside_serial_lock() {
const NAME1: &str = "assert_serially_locked_outside_serial_lock-NAME1";
const NAME2: &str = "assert_serially_locked_outside_serial_lock-NAME2";
assert!(!is_locked_serially(Some(NAME1)));
assert!(!is_locked_serially(Some(NAME2)));
local_serial_core(vec![NAME1], None, || {
// ...
});
assert!(!is_locked_serially(Some(NAME1)));
assert!(!is_locked_serially(Some(NAME2)));
}
#[test]
fn assert_serially_locked_in_different_thread() {
const NAME1: &str = "assert_serially_locked_in_different_thread-NAME1";
const NAME2: &str = "assert_serially_locked_in_different_thread-NAME2";
local_serial_core(vec![NAME1, NAME2], None, || {
std::thread::spawn(|| {
assert!(!is_locked_serially(Some(NAME2)));
})
.join()
.unwrap();
});
}
}
+327
View File
@@ -0,0 +1,327 @@
use fslock::LockFile;
#[cfg(feature = "logging")]
use log::debug;
use std::{
env,
fs::{self, File},
io::{Read, Write},
path::Path,
thread,
time::Duration,
};
pub(crate) struct Lock {
lockfile: LockFile,
pub(crate) parallel_count: u32,
path: String,
}
impl Lock {
// Can't use the same file as fslock truncates it
fn gen_count_file(path: &str) -> String {
format!("{}-count", path)
}
fn read_parallel_count(path: &str) -> u32 {
let parallel_count = match File::open(Lock::gen_count_file(path)) {
Ok(mut file) => {
let mut count_buf = [0; 4];
match file.read_exact(&mut count_buf) {
Ok(_) => u32::from_ne_bytes(count_buf),
Err(_err) => {
#[cfg(feature = "logging")]
debug!("Error loading count file: {}", _err);
0u32
}
}
}
Err(_) => 0,
};
#[cfg(feature = "logging")]
debug!("Parallel count for {:?} is {}", path, parallel_count);
parallel_count
}
fn create_lockfile(path: &str) -> LockFile {
if !Path::new(path).exists() {
fs::write(path, "").unwrap_or_else(|_| panic!("Lock file path was {:?}", path))
}
LockFile::open(path).unwrap()
}
pub(crate) fn new(path: &str) -> Lock {
#[cfg(feature = "test_logging")]
let _ = env_logger::builder().try_init();
let mut lockfile = Self::create_lockfile(path);
#[cfg(feature = "logging")]
debug!("Waiting on {:?}", path);
lockfile.lock().unwrap();
#[cfg(feature = "logging")]
debug!("Locked for {:?}", path);
Lock {
lockfile,
parallel_count: Lock::read_parallel_count(path),
path: String::from(path),
}
}
pub(crate) fn is_locked(path: &str) -> bool {
let mut lockfile = Self::create_lockfile(path);
#[cfg(feature = "logging")]
debug!("Checking lock on {:?}", path);
if lockfile
.try_lock()
.expect("try_lock shouldn't generally fail, please provide a bug report")
{
#[cfg(feature = "test_logging")]
debug!("{:?} wasn't locked", path);
lockfile
.unlock()
.expect("unlock shouldn't generally fail, please provide a bug report");
false
} else {
#[cfg(feature = "test_logging")]
debug!("{:?} was locked", path);
true
}
}
pub(crate) fn start_serial(self: &mut Lock) {
loop {
if self.parallel_count == 0 {
return;
}
#[cfg(feature = "logging")]
debug!("Waiting because parallel count is {}", self.parallel_count);
// unlock here is safe because we re-lock before returning
self.unlock();
thread::sleep(Duration::from_millis(50));
self.lockfile
.lock()
.expect("unlock shouldn't generally fail, please provide a bug report");
#[cfg(feature = "logging")]
debug!("Locked for {:?}", self.path);
self.parallel_count = Lock::read_parallel_count(&self.path)
}
}
fn unlock(self: &mut Lock) {
#[cfg(feature = "logging")]
debug!("Unlocking {}", self.path);
self.lockfile.unlock().unwrap();
}
pub(crate) fn end_serial(mut self: Lock) {
self.unlock();
}
fn write_parallel(self: &Lock) {
let mut file = File::create(&Lock::gen_count_file(&self.path)).unwrap();
file.write_all(&self.parallel_count.to_ne_bytes()).unwrap();
}
pub(crate) fn start_parallel(self: &mut Lock) {
self.parallel_count += 1;
self.write_parallel();
self.unlock();
}
pub(crate) fn end_parallel(mut self: Lock) {
assert!(self.parallel_count > 0);
self.parallel_count -= 1;
self.write_parallel();
self.unlock();
}
}
pub(crate) fn path_for_name(name: &str) -> String {
let mut pathbuf = env::temp_dir();
pathbuf.push(format!("serial-test-{}", name));
pathbuf.into_os_string().into_string().unwrap()
}
fn make_lock_for_name_and_path(name: &str, path_str: Option<&str>) -> Lock {
if let Some(opt_path) = path_str {
#[cfg(feature = "logging")]
{
let path = Path::new(opt_path);
if !path.is_absolute() {
debug!(
"Non-absolute path {opt_path} becomes {:?}",
path.canonicalize().unwrap_or_default()
);
}
}
Lock::new(opt_path)
} else {
let default_path = path_for_name(name);
Lock::new(&default_path)
}
}
pub(crate) fn get_locks(names: &Vec<&str>, path: Option<&str>) -> Vec<Lock> {
#[cfg(feature = "test_logging")]
let _ = env_logger::builder().try_init();
if names.len() > 1 && path.is_some() {
panic!("Can't do file_serial/parallel with both more than one name _and_ a specific path");
}
names
.iter()
.map(|name| make_lock_for_name_and_path(name, path))
.collect::<Vec<_>>()
}
/// Check if the current thread is holding a `file_serial` lock
///
/// Can be used to assert that a piece of code can only be called
/// from a test marked `#[file_serial]`.
///
/// Example, with `#[file_serial]`:
///
/// ```no_run
/// use serial_test::{is_locked_file_serially, file_serial};
///
/// fn do_something_in_need_of_serialization() {
/// assert!(is_locked_file_serially(None, None));
///
/// // ...
/// }
///
/// #[test]
/// # fn unused() {}
/// #[file_serial]
/// fn main() {
/// do_something_in_need_of_serialization();
/// }
/// ```
///
/// Example, missing `#[file_serial]`:
///
/// ```no_run
/// use serial_test::{is_locked_file_serially, file_serial};
///
/// #[test]
/// # fn unused() {}
/// // #[file_serial] // <-- missing
/// fn main() {
/// assert!(is_locked_file_serially(None, None));
/// }
/// ```
///
/// Example, `#[test(some_key)]`:
///
/// ```no_run
/// use serial_test::{is_locked_file_serially, file_serial};
///
/// #[test]
/// # fn unused() {}
/// #[file_serial(some_key)]
/// fn main() {
/// assert!(is_locked_file_serially(Some("some_key"), None));
/// assert!(!is_locked_file_serially(None, None));
/// }
/// ```
pub fn is_locked_file_serially(name: Option<&str>, path: Option<&str>) -> bool {
if let Some(opt_path) = path {
Lock::is_locked(opt_path)
} else {
let default_path = path_for_name(name.unwrap_or_default());
Lock::is_locked(&default_path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{fs_parallel_core, fs_serial_core};
fn init() {
#[cfg(feature = "test_logging")]
let _ = env_logger::builder().is_test(false).try_init();
}
#[test]
fn assert_serially_locked_without_name() {
init();
fs_serial_core(vec![""], None, || {
assert!(is_locked_file_serially(None, None));
assert!(!is_locked_file_serially(
Some("no_such_name_assert_serially_locked_without_name"),
None
));
});
}
#[test]
fn assert_serially_locked_with_multiple_names() {
const NAME1: &str = "assert_serially_locked_with_multiple_names-NAME1";
const NAME2: &str = "assert_serially_locked_with_multiple_names-NAME2";
init();
fs_serial_core(vec![NAME1, NAME2], None, || {
assert!(is_locked_file_serially(Some(NAME1), None));
assert!(is_locked_file_serially(Some(NAME2), None));
assert!(!is_locked_file_serially(
Some("no_such_name_assert_serially_locked_with_multiple_names"),
None
));
});
}
#[test]
fn assert_serially_locked_when_actually_locked_parallel() {
const NAME1: &str = "assert_serially_locked_when_actually_locked_parallel-NAME1";
const NAME2: &str = "assert_serially_locked_when_actually_locked_parallel-NAME2";
init();
fs_parallel_core(vec![NAME1, NAME2], None, || {
assert!(!is_locked_file_serially(Some(NAME1), None));
assert!(!is_locked_file_serially(Some(NAME2), None));
assert!(!is_locked_file_serially(
Some("no_such_name_assert_serially_locked_when_actually_locked_parallel"),
None
));
});
}
#[test]
fn assert_serially_locked_outside_serial_lock() {
const NAME1: &str = "assert_serially_locked_outside_serial_lock-NAME1";
const NAME2: &str = "assert_serially_locked_outside_serial_lock-NAME2";
init();
assert!(!is_locked_file_serially(Some(NAME1), None));
assert!(!is_locked_file_serially(Some(NAME2), None));
fs_serial_core(vec![NAME1], None, || {
// ...
});
assert!(!is_locked_file_serially(Some(NAME1), None));
assert!(!is_locked_file_serially(Some(NAME2), None));
}
#[test]
fn assert_serially_locked_in_different_thread() {
const NAME1: &str = "assert_serially_locked_in_different_thread-NAME1";
const NAME2: &str = "assert_serially_locked_in_different_thread-NAME2";
init();
fs_serial_core(vec![NAME1, NAME2], None, || {
std::thread::spawn(|| {
assert!(is_locked_file_serially(Some(NAME2), None));
})
.join()
.unwrap();
});
}
}
+157
View File
@@ -0,0 +1,157 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(unused_variables)]
#![deny(missing_docs)]
#![deny(unused_imports)]
//! # serial_test
//! `serial_test` allows for the creation of serialised Rust tests using the [serial](macro@serial) attribute
//! e.g.
//! ````no_run
//! #[test]
//! #[serial]
//! fn test_serial_one() {
//! // Do things
//! }
//!
//! #[test]
//! #[serial(some_key)]
//! fn test_serial_another() {
//! // Do things
//! }
//!
//! #[test]
//! #[parallel]
//! fn test_parallel_another() {
//! // Do parallel things
//! }
//! ````
//! Multiple tests with the [serial](macro@serial) attribute are guaranteed to be executed in serial. Ordering
//! of the tests is not guaranteed however. Other tests with the [parallel](macro@parallel) attribute may run
//! at the same time as each other, but not at the same time as a test with [serial](macro@serial). Tests with
//! neither attribute may run at any time and no guarantees are made about their timing!
//!
//! For cases like doctests and integration tests where the tests are run as separate processes, we also support
//! [file_serial](macro@file_serial)/[file_parallel](macro@file_parallel), with similar properties but based off file locking. Note that there are no
//! guarantees about one test with [serial](macro@serial)/[parallel](macro@parallel) and another with [file_serial](macro@file_serial)/[file_parallel](macro@file_parallel)
//! as they lock using different methods.
//! ````no_run
//! #[test]
//! #[file_serial]
//! fn test_serial_three() {
//! // Do things
//! }
//! ````
//!
//! All of the attributes can also be applied at a `mod` level and will be automagically applied to all test functions in that block
//! ````no_run
//! #[cfg(test)]
//! #[serial]
//! mod serial_attr_tests {
//! fn foo() {
//! // Won't have `serial` applied, because not a test function
//! println!("Nothing");
//! }
//!
//! #[test]
//! fn test_bar() {
//! // Will be run serially
//! }
//!}
//! ````
//!
//! All of the attributes support an optional `crate` argument for other macros generating
//! the attributes, which lets them re-export the serial_test crate and supply an import path.
//! This defaults to assuming it can just import `serial_test` for the use of internal functions.
//! Note this is `crate = <import-path>` not `crate => <import-path>` unlike the `path` in [file_serial](macro@file_serial)
//! for historical reasons
//! ````no_run
//! // Assuming wrapper::refs:serial is a re-export of serial_test
//! #[test]
//! #[serial(crate = wrapper::refs:serial)]
//! fn test_generated() {
//! // Do things
//! }
//! ````
//!
//! ## Inner Attributes
//!
//! You can apply attributes to an inner test function using `inner_attrs`. This is useful for
//! applying attributes like `ntest::timeout` that should only affect the test body, not the
//! mutex/lock acquisition. Without this, a timeout would start counting from when the test is
//! waiting for the lock, not when the actual test logic starts.
//!
//! ````no_run
//! #[test]
//! #[serial(inner_attrs = [ntest::timeout(1000)])]
//! fn test_with_timeout() {
//! // The timeout only applies to this body, not the serial lock acquisition
//! }
//! ````
//!
//! You can combine `inner_attrs` with keys and other options:
//! ````no_run
//! #[test]
//! #[serial(my_key, inner_attrs = [ntest::timeout(1000)])]
//! fn test_with_key_and_timeout() {
//! // Serialized with 'my_key' group, with timeout on the body
//! }
//! ````
//!
//! ## Feature flags
#![cfg_attr(
feature = "docsrs",
cfg_attr(doc, doc = ::document_features::document_features!())
)]
mod code_lock;
mod parallel_code_lock;
mod rwlock;
mod serial_code_lock;
#[cfg(feature = "file_locks")]
mod file_lock;
#[cfg(feature = "file_locks")]
mod parallel_file_lock;
#[cfg(feature = "file_locks")]
mod serial_file_lock;
#[cfg(feature = "async")]
#[doc(hidden)]
pub use parallel_code_lock::{local_async_parallel_core, local_async_parallel_core_with_return};
#[doc(hidden)]
pub use parallel_code_lock::{local_parallel_core, local_parallel_core_with_return};
#[cfg(feature = "async")]
#[doc(hidden)]
pub use serial_code_lock::{local_async_serial_core, local_async_serial_core_with_return};
#[doc(hidden)]
pub use serial_code_lock::{local_serial_core, local_serial_core_with_return};
#[cfg(all(feature = "file_locks", feature = "async"))]
#[doc(hidden)]
pub use serial_file_lock::{fs_async_serial_core, fs_async_serial_core_with_return};
#[cfg(feature = "file_locks")]
#[doc(hidden)]
pub use serial_file_lock::{fs_serial_core, fs_serial_core_with_return};
#[cfg(feature = "file_locks")]
pub use file_lock::is_locked_file_serially;
#[cfg(all(feature = "file_locks", feature = "async"))]
#[doc(hidden)]
pub use parallel_file_lock::{fs_async_parallel_core, fs_async_parallel_core_with_return};
#[cfg(feature = "file_locks")]
#[doc(hidden)]
pub use parallel_file_lock::{fs_parallel_core, fs_parallel_core_with_return};
// Re-export #[serial/parallel].
pub use serial_test_derive::{parallel, serial};
#[cfg(feature = "file_locks")]
pub use serial_test_derive::{file_parallel, file_serial};
pub use code_lock::is_locked_serially;
@@ -0,0 +1,195 @@
#![allow(clippy::await_holding_lock)]
use crate::code_lock::{check_new_key, global_locks};
#[cfg(feature = "async")]
use futures_util::FutureExt;
use std::panic;
fn get_locks(names: Vec<&str>) -> Vec<crate::code_lock::UniqueReentrantMutex> {
names
.into_iter()
.map(|name| {
check_new_key(name);
global_locks()
.get(name)
.expect("key to be set")
.get()
.clone()
})
.collect::<Vec<_>>()
}
#[doc(hidden)]
pub fn local_parallel_core_with_return<E>(
names: Vec<&str>,
_path: Option<&str>,
function: fn() -> Result<(), E>,
) -> Result<(), E> {
let locks = get_locks(names);
locks.iter().for_each(|lock| lock.start_parallel());
let res = panic::catch_unwind(function);
locks.iter().for_each(|lock| lock.end_parallel());
match res {
Ok(ret) => ret,
Err(err) => {
panic::resume_unwind(err);
}
}
}
#[doc(hidden)]
pub fn local_parallel_core(names: Vec<&str>, _path: Option<&str>, function: fn()) {
let locks = get_locks(names);
locks.iter().for_each(|lock| lock.start_parallel());
let res = panic::catch_unwind(|| {
function();
});
locks.iter().for_each(|lock| lock.end_parallel());
if let Err(err) = res {
panic::resume_unwind(err);
}
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn local_async_parallel_core_with_return<E>(
names: Vec<&str>,
_path: Option<&str>,
fut: impl std::future::Future<Output = Result<(), E>> + panic::UnwindSafe,
) -> Result<(), E> {
let locks = get_locks(names);
locks.iter().for_each(|lock| lock.start_parallel());
let res = fut.catch_unwind().await;
locks.iter().for_each(|lock| lock.end_parallel());
match res {
Ok(ret) => ret,
Err(err) => {
panic::resume_unwind(err);
}
}
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn local_async_parallel_core(
names: Vec<&str>,
_path: Option<&str>,
fut: impl std::future::Future<Output = ()> + panic::UnwindSafe,
) {
let locks = get_locks(names);
locks.iter().for_each(|lock| lock.start_parallel());
let res = fut.catch_unwind().await;
locks.iter().for_each(|lock| lock.end_parallel());
if let Err(err) = res {
panic::resume_unwind(err);
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "async")]
use crate::{local_async_parallel_core, local_async_parallel_core_with_return};
use crate::{code_lock::global_locks, local_parallel_core, local_parallel_core_with_return};
use std::{io::Error, panic};
#[test]
fn unlock_on_assert_sync_without_return() {
let _ = panic::catch_unwind(|| {
local_parallel_core(vec!["unlock_on_assert_sync_without_return"], None, || {
assert!(false);
})
});
assert_eq!(
global_locks()
.get("unlock_on_assert_sync_without_return")
.unwrap()
.get()
.parallel_count(),
0
);
}
#[test]
fn unlock_on_assert_sync_with_return() {
let _ = panic::catch_unwind(|| {
local_parallel_core_with_return(
vec!["unlock_on_assert_sync_with_return"],
None,
|| -> Result<(), Error> {
assert!(false);
Ok(())
},
)
});
assert_eq!(
global_locks()
.get("unlock_on_assert_sync_with_return")
.unwrap()
.get()
.parallel_count(),
0
);
}
#[test]
#[cfg(feature = "async")]
fn unlock_on_assert_async_without_return() {
async fn demo_assert() {
assert!(false);
}
async fn call_serial_test_fn() {
local_async_parallel_core(
vec!["unlock_on_assert_async_without_return"],
None,
demo_assert(),
)
.await
}
// as per https://stackoverflow.com/a/66529014/320546
let _ = panic::catch_unwind(|| {
futures_executor::block_on(call_serial_test_fn());
});
assert_eq!(
global_locks()
.get("unlock_on_assert_async_without_return")
.unwrap()
.get()
.parallel_count(),
0
);
}
#[test]
#[cfg(feature = "async")]
fn unlock_on_assert_async_with_return() {
async fn demo_assert() -> Result<(), Error> {
assert!(false);
Ok(())
}
#[allow(unused_must_use)]
async fn call_serial_test_fn() {
local_async_parallel_core_with_return(
vec!["unlock_on_assert_async_with_return"],
None,
demo_assert(),
)
.await;
}
// as per https://stackoverflow.com/a/66529014/320546
let _ = panic::catch_unwind(|| {
futures_executor::block_on(call_serial_test_fn());
});
assert_eq!(
global_locks()
.get("unlock_on_assert_async_with_return")
.unwrap()
.get()
.parallel_count(),
0
);
}
}
@@ -0,0 +1,181 @@
use std::panic;
#[cfg(feature = "async")]
use futures_util::FutureExt;
use crate::file_lock::get_locks;
#[doc(hidden)]
pub fn fs_parallel_core(names: Vec<&str>, path: Option<&str>, function: fn()) {
get_locks(&names, path)
.iter_mut()
.for_each(|lock| lock.start_parallel());
let res = panic::catch_unwind(|| {
function();
});
get_locks(&names, path)
.into_iter()
.for_each(|lock| lock.end_parallel());
if let Err(err) = res {
panic::resume_unwind(err);
}
}
#[doc(hidden)]
pub fn fs_parallel_core_with_return<E>(
names: Vec<&str>,
path: Option<&str>,
function: fn() -> Result<(), E>,
) -> Result<(), E> {
get_locks(&names, path)
.iter_mut()
.for_each(|lock| lock.start_parallel());
let res = panic::catch_unwind(function);
get_locks(&names, path)
.into_iter()
.for_each(|lock| lock.end_parallel());
match res {
Ok(ret) => ret,
Err(err) => {
panic::resume_unwind(err);
}
}
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn fs_async_parallel_core_with_return<E>(
names: Vec<&str>,
path: Option<&str>,
fut: impl std::future::Future<Output = Result<(), E>> + panic::UnwindSafe,
) -> Result<(), E> {
get_locks(&names, path)
.iter_mut()
.for_each(|lock| lock.start_parallel());
let res = fut.catch_unwind().await;
get_locks(&names, path)
.into_iter()
.for_each(|lock| lock.end_parallel());
match res {
Ok(ret) => ret,
Err(err) => {
panic::resume_unwind(err);
}
}
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn fs_async_parallel_core(
names: Vec<&str>,
path: Option<&str>,
fut: impl std::future::Future<Output = ()> + panic::UnwindSafe,
) {
get_locks(&names, path)
.iter_mut()
.for_each(|lock| lock.start_parallel());
let res = fut.catch_unwind().await;
get_locks(&names, path)
.into_iter()
.for_each(|lock| lock.end_parallel());
if let Err(err) = res {
panic::resume_unwind(err);
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "async")]
use crate::{fs_async_parallel_core, fs_async_parallel_core_with_return};
use crate::{
file_lock::{path_for_name, Lock},
fs_parallel_core, fs_parallel_core_with_return,
};
use std::{io::Error, panic};
fn unlock_ok(lock_path: &str) {
let lock = Lock::new(lock_path);
assert_eq!(lock.parallel_count, 0);
}
#[test]
fn unlock_on_assert_sync_without_return() {
let lock_path = path_for_name("parallel_unlock_on_assert_sync_without_return");
let _ = panic::catch_unwind(|| {
fs_parallel_core(
vec!["parallel_unlock_on_assert_sync_without_return"],
Some(&lock_path),
|| {
assert!(false);
},
)
});
unlock_ok(&lock_path);
}
#[test]
fn unlock_on_assert_sync_with_return() {
let lock_path = path_for_name("unlock_on_assert_sync_with_return");
let _ = panic::catch_unwind(|| {
fs_parallel_core_with_return(
vec!["unlock_on_assert_sync_with_return"],
Some(&lock_path),
|| -> Result<(), Error> {
assert!(false);
Ok(())
},
)
});
unlock_ok(&lock_path);
}
#[test]
#[cfg(feature = "async")]
fn unlock_on_assert_async_without_return() {
let lock_path = path_for_name("unlock_on_assert_async_without_return");
async fn demo_assert() {
assert!(false);
}
async fn call_serial_test_fn(lock_path: &str) {
fs_async_parallel_core(
vec!["unlock_on_assert_async_without_return"],
Some(&lock_path),
demo_assert(),
)
.await
}
let _ = panic::catch_unwind(|| {
futures_executor::block_on(call_serial_test_fn(&lock_path));
});
unlock_ok(&lock_path);
}
#[test]
#[cfg(feature = "async")]
fn unlock_on_assert_async_with_return() {
let lock_path = path_for_name("unlock_on_assert_async_with_return");
async fn demo_assert() -> Result<(), Error> {
assert!(false);
Ok(())
}
#[allow(unused_must_use)]
async fn call_serial_test_fn(lock_path: &str) {
fs_async_parallel_core_with_return(
vec!["unlock_on_assert_async_with_return"],
Some(&lock_path),
demo_assert(),
)
.await;
}
let _ = panic::catch_unwind(|| {
futures_executor::block_on(call_serial_test_fn(&lock_path));
});
unlock_ok(&lock_path);
}
}
+138
View File
@@ -0,0 +1,138 @@
#[cfg(feature = "logging")]
use log::debug;
use parking_lot::{Condvar, Mutex, ReentrantMutex, ReentrantMutexGuard};
use std::{sync::Arc, time::Duration};
struct LockState {
parallels: u32,
}
struct LockData {
mutex: Mutex<LockState>,
serial: ReentrantMutex<()>,
condvar: Condvar,
}
#[derive(Clone)]
pub(crate) struct Locks {
arc: Arc<LockData>,
// Name we're locking for (mostly test usage)
#[cfg(feature = "logging")]
pub(crate) name: String,
}
pub(crate) struct MutexGuardWrapper<'a> {
#[allow(dead_code)] // need it around to get dropped
mutex_guard: ReentrantMutexGuard<'a, ()>,
locks: Locks,
}
impl Drop for MutexGuardWrapper<'_> {
fn drop(&mut self) {
#[cfg(feature = "logging")]
debug!("End serial");
self.locks.arc.condvar.notify_one();
}
}
impl Locks {
#[allow(unused_variables)]
pub fn new(name: &str) -> Locks {
Locks {
arc: Arc::new(LockData {
mutex: Mutex::new(LockState { parallels: 0 }),
condvar: Condvar::new(),
serial: Default::default(),
}),
#[cfg(feature = "logging")]
name: name.to_owned(),
}
}
#[cfg(test)]
pub fn is_locked(&self) -> bool {
self.arc.serial.is_locked()
}
pub fn is_locked_by_current_thread(&self) -> bool {
self.arc.serial.is_owned_by_current_thread()
}
pub fn serial(&self) -> MutexGuardWrapper<'_> {
#[cfg(feature = "logging")]
debug!("Get serial lock '{}'", self.name);
let mut lock_state = self.arc.mutex.lock();
loop {
#[cfg(feature = "logging")]
debug!("Serial acquire {} {}", lock_state.parallels, self.name);
// If all the things we want are true, try to lock out serial
if lock_state.parallels == 0 {
let possible_serial_lock = self.arc.serial.try_lock();
if let Some(serial_lock) = possible_serial_lock {
#[cfg(feature = "logging")]
debug!("Got serial '{}'", self.name);
return MutexGuardWrapper {
mutex_guard: serial_lock,
locks: self.clone(),
};
} else {
#[cfg(feature = "logging")]
debug!("Someone else has serial '{}'", self.name);
}
}
self.arc
.condvar
.wait_for(&mut lock_state, Duration::from_secs(1));
}
}
pub fn start_parallel(&self) {
#[cfg(feature = "logging")]
debug!("Get parallel lock '{}'", self.name);
let mut lock_state = self.arc.mutex.lock();
loop {
#[cfg(feature = "logging")]
debug!(
"Parallel, existing {} '{}'",
lock_state.parallels, self.name
);
if lock_state.parallels > 0 {
// fast path, as someone else already has it locked
lock_state.parallels += 1;
return;
}
let possible_serial_lock = self.arc.serial.try_lock();
if possible_serial_lock.is_some() {
#[cfg(feature = "logging")]
debug!("Parallel first '{}'", self.name);
// We now know no-one else has the serial lock, so we can add to parallel
lock_state.parallels = 1; // Had to have been 0 before, as otherwise we'd have hit the fast path
return;
}
#[cfg(feature = "logging")]
debug!("Parallel waiting '{}'", self.name);
self.arc
.condvar
.wait_for(&mut lock_state, Duration::from_secs(1));
}
}
pub fn end_parallel(&self) {
#[cfg(feature = "logging")]
debug!("End parallel '{}", self.name);
let mut lock_state = self.arc.mutex.lock();
assert!(lock_state.parallels > 0);
lock_state.parallels -= 1;
drop(lock_state);
self.arc.condvar.notify_one();
}
#[cfg(test)]
pub fn parallel_count(&self) -> u32 {
let lock_state = self.arc.mutex.lock();
lock_state.parallels
}
}
+122
View File
@@ -0,0 +1,122 @@
#![allow(clippy::await_holding_lock)]
use crate::code_lock::{check_new_key, global_locks};
#[doc(hidden)]
macro_rules! core_internal {
($names: ident) => {
let unlocks: Vec<_> = $names
.into_iter()
.map(|name| {
check_new_key(name);
global_locks()
.get(name)
.expect("key to be set")
.get()
.clone()
})
.collect();
let _guards: Vec<_> = unlocks.iter().map(|unlock| unlock.lock()).collect();
};
}
#[doc(hidden)]
pub fn local_serial_core_with_return<R, E>(
names: Vec<&str>,
_path: Option<String>,
function: fn() -> Result<R, E>,
) -> Result<R, E> {
core_internal!(names);
function()
}
#[doc(hidden)]
pub fn local_serial_core(names: Vec<&str>, _path: Option<&str>, function: fn()) {
core_internal!(names);
function();
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn local_async_serial_core_with_return<R, E>(
names: Vec<&str>,
_path: Option<&str>,
fut: impl std::future::Future<Output = Result<R, E>> + std::marker::Send,
) -> Result<R, E> {
core_internal!(names);
fut.await
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn local_async_serial_core(
names: Vec<&str>,
_path: Option<&str>,
fut: impl std::future::Future<Output = ()>,
) {
core_internal!(names);
fut.await;
}
#[cfg(test)]
#[allow(clippy::print_stdout)]
mod tests {
use super::local_serial_core;
use crate::code_lock::{check_new_key, global_locks};
use itertools::Itertools;
use parking_lot::RwLock;
use std::{
sync::{Arc, Barrier},
thread,
time::Duration,
};
#[test]
fn test_hammer_check_new_key() {
let ptrs = Arc::new(RwLock::new(Vec::new()));
let mut threads = Vec::new();
let count = 100;
let barrier = Arc::new(Barrier::new(count));
for _ in 0..count {
let local_locks = global_locks();
let local_ptrs = ptrs.clone();
let c = barrier.clone();
threads.push(thread::spawn(move || {
c.wait();
check_new_key("foo");
{
let unlock = local_locks.get("foo").expect("read didn't work");
let mutex = unlock.get();
let mut ptr_guard = local_ptrs
.try_write_for(Duration::from_secs(1))
.expect("write lock didn't work");
ptr_guard.push(mutex.id);
}
c.wait();
}));
}
for thread in threads {
thread.join().expect("thread join worked");
}
let ptrs_read_lock = ptrs
.try_read_recursive_for(Duration::from_secs(1))
.expect("ptrs read work");
assert_eq!(ptrs_read_lock.len(), count);
println!("{:?}", ptrs_read_lock);
assert_eq!(ptrs_read_lock.iter().unique().count(), 1);
}
#[test]
fn unlock_on_assert() {
let _ = std::panic::catch_unwind(|| {
local_serial_core(vec!["assert"], None, || {
assert!(false);
})
});
assert!(!global_locks().get("assert").unwrap().get().is_locked());
}
}
@@ -0,0 +1,91 @@
use std::panic;
use crate::file_lock::get_locks;
#[doc(hidden)]
pub fn fs_serial_core(names: Vec<&str>, path: Option<&str>, function: fn()) {
assert!(names.len() > 0);
let mut locks = get_locks(&names, path);
locks.iter_mut().for_each(|lock| lock.start_serial());
let res = panic::catch_unwind(function);
locks.into_iter().for_each(|lock| lock.end_serial());
if let Err(err) = res {
panic::resume_unwind(err);
}
}
#[doc(hidden)]
pub fn fs_serial_core_with_return<E>(
names: Vec<&str>,
path: Option<&str>,
function: fn() -> Result<(), E>,
) -> Result<(), E> {
let mut locks = get_locks(&names, path);
locks.iter_mut().for_each(|lock| lock.start_serial());
let res = panic::catch_unwind(function);
locks.into_iter().for_each(|lock| lock.end_serial());
match res {
Ok(ret) => ret,
Err(err) => {
panic::resume_unwind(err);
}
}
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn fs_async_serial_core_with_return<E>(
names: Vec<&str>,
path: Option<&str>,
fut: impl std::future::Future<Output = Result<(), E>>,
) -> Result<(), E> {
let mut locks = get_locks(&names, path);
locks.iter_mut().for_each(|lock| lock.start_serial());
let ret: Result<(), E> = fut.await;
locks.into_iter().for_each(|lock| lock.end_serial());
ret
}
#[doc(hidden)]
#[cfg(feature = "async")]
pub async fn fs_async_serial_core(
names: Vec<&str>,
path: Option<&str>,
fut: impl std::future::Future<Output = ()>,
) {
let mut locks = get_locks(&names, path);
locks.iter_mut().for_each(|lock| lock.start_serial());
fut.await;
locks.into_iter().for_each(|lock| lock.end_serial());
}
#[cfg(test)]
mod tests {
use std::panic;
use fslock::LockFile;
use super::fs_serial_core;
use crate::file_lock::path_for_name;
#[test]
fn test_serial() {
fs_serial_core(vec!["test"], None, || {});
}
#[test]
fn unlock_on_assert_sync_without_return() {
let lock_path = path_for_name("serial_unlock_on_assert_sync_without_return");
let _ = panic::catch_unwind(|| {
fs_serial_core(
vec!["serial_unlock_on_assert_sync_without_return"],
Some(&lock_path),
|| {
assert!(false);
},
)
});
let mut lockfile = LockFile::open(&lock_path).unwrap();
assert!(lockfile.try_lock().unwrap());
}
}
+8
View File
@@ -0,0 +1,8 @@
use serial_test::local_serial_core;
#[test]
fn test_empty_serial_call() {
local_serial_core(vec!["beta"], None, || {
println!("Bar");
});
}