Skip to content

Commit f8378e5

Browse files
RUST-485 Unified test runner (#249)
1 parent 689ac0a commit f8378e5

20 files changed

+3887
-32
lines changed

src/bson_util/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ where
8686
Ok(millis.map(Duration::from_millis))
8787
}
8888

89+
pub(crate) fn deserialize_duration_from_u64_seconds<'de, D>(
90+
deserializer: D,
91+
) -> std::result::Result<Option<Duration>, D::Error>
92+
where
93+
D: Deserializer<'de>,
94+
{
95+
let millis = Option::<u64>::deserialize(deserializer)?;
96+
Ok(millis.map(Duration::from_secs))
97+
}
98+
8999
#[allow(clippy::trivially_copy_pass_by_ref)]
90100
pub(crate) fn serialize_u32_as_i32<S: Serializer>(
91101
val: &Option<u32>,

src/coll/options.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::time::Duration;
22

3-
use serde::{Deserialize, Serialize, Serializer};
3+
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
44
use serde_with::skip_serializing_none;
55
use typed_builder::TypedBuilder;
66

@@ -40,7 +40,7 @@ pub struct CollectionOptions {
4040
/// [`Collection::find_one_and_replace`](../struct.Collection.html#method.find_one_and_replace) and
4141
/// [`Collection::find_one_and_update`](../struct.Collection.html#method.find_one_and_update)
4242
/// operation should return the document before or after modification.
43-
#[derive(Clone, Debug, Deserialize)]
43+
#[derive(Clone, Debug)]
4444
#[non_exhaustive]
4545
pub enum ReturnDocument {
4646
/// Return the document after modification.
@@ -49,6 +49,20 @@ pub enum ReturnDocument {
4949
Before,
5050
}
5151

52+
impl<'de> Deserialize<'de> for ReturnDocument {
53+
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
54+
let s = String::deserialize(deserializer)?;
55+
match s.to_lowercase().as_str() {
56+
"after" => Ok(ReturnDocument::After),
57+
"before" => Ok(ReturnDocument::Before),
58+
other => Err(D::Error::custom(format!(
59+
"Unknown return document value: {}",
60+
other
61+
))),
62+
}
63+
}
64+
}
65+
5266
/// Specifies the index to use for an operation.
5367
#[derive(Clone, Debug, Deserialize, Serialize)]
5468
#[serde(untagged)]
@@ -288,6 +302,7 @@ pub struct DeleteOptions {
288302
/// [`Collection::find_one_and_delete`](../struct.Collection.html#method.find_one_and_delete)
289303
/// operation.
290304
#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)]
305+
#[serde(rename_all = "camelCase")]
291306
#[non_exhaustive]
292307
pub struct FindOneAndDeleteOptions {
293308
/// The maximum amount of time to allow the query to run.
@@ -326,6 +341,7 @@ pub struct FindOneAndDeleteOptions {
326341
/// [`Collection::find_one_and_replace`](../struct.Collection.html#method.find_one_and_replace)
327342
/// operation.
328343
#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)]
344+
#[serde(rename_all = "camelCase")]
329345
#[non_exhaustive]
330346
pub struct FindOneAndReplaceOptions {
331347
/// Opt out of document-level validation.
@@ -376,6 +392,7 @@ pub struct FindOneAndReplaceOptions {
376392
/// [`Collection::find_one_and_update`](../struct.Collection.html#method.find_one_and_update)
377393
/// operation.
378394
#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)]
395+
#[serde(rename_all = "camelCase")]
379396
#[non_exhaustive]
380397
pub struct FindOneAndUpdateOptions {
381398
/// A set of filters specifying to which array elements an update should apply.

src/error.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ impl ErrorKind {
368368

369369
/// Gets the code/message tuple from this error, if applicable. In the case of write errors, the
370370
/// code and message are taken from the write concern error, if there is one.
371-
fn code_and_message(&self) -> Option<(i32, &str)> {
371+
pub(crate) fn code_and_message(&self) -> Option<(i32, &str)> {
372372
match self {
373373
ErrorKind::CommandError(ref cmd_err) => Some((cmd_err.code, cmd_err.message.as_str())),
374374
ErrorKind::WriteError(WriteFailure::WriteConcernError(ref wc_err)) => {
@@ -382,6 +382,22 @@ impl ErrorKind {
382382
}
383383
}
384384

385+
/// Gets the code name from this error, if applicable.
386+
#[cfg(test)]
387+
pub(crate) fn code_name(&self) -> Option<&str> {
388+
match self {
389+
ErrorKind::CommandError(ref cmd_err) => Some(cmd_err.code_name.as_str()),
390+
ErrorKind::WriteError(ref failure) => {
391+
match failure {
392+
WriteFailure::WriteConcernError(ref wce) => Some(wce.code_name.as_str()),
393+
WriteFailure::WriteError(ref we) => we.code_name.as_deref(),
394+
}
395+
}
396+
ErrorKind::BulkWriteError(ref bwe) => bwe.write_concern_error.as_ref().map(|wce| wce.code_name.as_str()),
397+
_ => None,
398+
}
399+
}
400+
385401
/// If this error corresponds to a "not master" error as per the SDAM spec.
386402
pub(crate) fn is_not_master(&self) -> bool {
387403
self.code_and_message()

src/selection_criteria.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
11
use std::{collections::HashMap, sync::Arc, time::Duration};
22

33
use derivative::Derivative;
4-
use serde::Deserialize;
4+
use serde::{de::Error, Deserialize, Deserializer};
55
use typed_builder::TypedBuilder;
66

77
use crate::{
88
bson::{doc, Bson, Document},
9+
bson_util::deserialize_duration_from_u64_seconds,
910
error::{ErrorKind, Result},
1011
options::StreamAddress,
1112
sdam::public::ServerInfo,
1213
};
1314

1415
/// Describes which servers are suitable for a given operation.
15-
#[derive(Clone, Derivative, Deserialize)]
16+
#[derive(Clone, Derivative)]
1617
#[derivative(Debug)]
17-
#[serde(untagged)]
1818
#[non_exhaustive]
1919
pub enum SelectionCriteria {
2020
/// A read preference that describes the suitable servers based on the server type, max
2121
/// staleness, and server tags.
2222
///
2323
/// See the documentation [here](https://docs.mongodb.com/manual/core/read-preference/) for more details.
24-
// TODO RUST-495: unskip for deserialization
25-
#[serde(skip)]
2624
ReadPreference(ReadPreference),
2725

2826
/// A predicate used to filter servers that are considered suitable. A `server` will be
2927
/// considered suitable by a `predicate` if `predicate(server)` returns true.
30-
#[serde(skip)]
3128
Predicate(#[derivative(Debug = "ignore")] Predicate),
3229
}
3330

@@ -75,6 +72,15 @@ impl SelectionCriteria {
7572
}
7673
}
7774

75+
impl<'de> Deserialize<'de> for SelectionCriteria {
76+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
77+
where
78+
D: Deserializer<'de>,
79+
{
80+
Ok(SelectionCriteria::ReadPreference(ReadPreference::deserialize(deserializer)?))
81+
}
82+
}
83+
7884
/// A predicate used to filter servers that are considered suitable.
7985
pub type Predicate = Arc<dyn Send + Sync + Fn(&ServerInfo) -> bool>;
8086

@@ -87,7 +93,6 @@ pub type Predicate = Arc<dyn Send + Sync + Fn(&ServerInfo) -> bool>;
8793
///
8894
/// See the [MongoDB docs](https://docs.mongodb.com/manual/core/read-preference) for more details.
8995
#[derive(Clone, Debug, PartialEq)]
90-
// TODO RUST-495: implement Deserialize for ReadPreference
9196
pub enum ReadPreference {
9297
/// Only route this operation to the primary.
9398
Primary,
@@ -106,8 +111,34 @@ pub enum ReadPreference {
106111
Nearest { options: ReadPreferenceOptions },
107112
}
108113

114+
impl<'de> Deserialize<'de> for ReadPreference {
115+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
116+
where
117+
D: Deserializer<'de>,
118+
{
119+
#[derive(Deserialize)]
120+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
121+
struct ReadPreferenceHelper {
122+
mode: String,
123+
#[serde(flatten)]
124+
options: ReadPreferenceOptions,
125+
}
126+
let preference = ReadPreferenceHelper::deserialize(deserializer)?;
127+
128+
match preference.mode.as_str() {
129+
"Primary" => Ok(ReadPreference::Primary),
130+
"Secondary" => Ok(ReadPreference::Secondary { options: preference.options }),
131+
"PrimaryPreferred" => Ok(ReadPreference::PrimaryPreferred { options: preference.options }),
132+
"SecondaryPreferred" => Ok(ReadPreference::SecondaryPreferred { options: preference.options }),
133+
"Nearest" => Ok(ReadPreference::Nearest { options: preference.options }),
134+
other => Err(D::Error::custom(format!("Unknown read preference mode: {}", other))),
135+
}
136+
}
137+
}
138+
109139
/// Specifies read preference options for non-primary read preferences.
110140
#[derive(Clone, Debug, Default, Deserialize, PartialEq, TypedBuilder)]
141+
#[serde(rename_all = "camelCase")]
111142
#[non_exhaustive]
112143
pub struct ReadPreferenceOptions {
113144
/// Specifies which replica set members should be considered for operations. Each tag set will
@@ -119,9 +150,10 @@ pub struct ReadPreferenceOptions {
119150
/// considered for the given operation. Any secondaries lagging behind more than
120151
/// `max_staleness` will not be considered for the operation.
121152
///
122-
/// `max_stalesness` must be at least 90 seconds. If a `max_stalness` less than 90 seconds is
153+
/// `max_staleness` must be at least 90 seconds. If a `max_staleness` less than 90 seconds is
123154
/// specified for an operation, the operation will return an error.
124155
#[builder(default)]
156+
#[serde(rename = "maxStalenessSeconds", deserialize_with = "deserialize_duration_from_u64_seconds")]
125157
pub max_staleness: Option<Duration>,
126158

127159
/// Specifies hedging behavior for reads. These options only apply to sharded clusters on

src/test/mod.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ mod spec;
1010
mod util;
1111

1212
pub(crate) use self::{
13-
spec::{run_spec_test, run_v2_test, AnyTestOperation, OperationObject, RunOn, TestEvent},
13+
spec::{
14+
run_spec_test,
15+
run_v2_test,
16+
AnyTestOperation,
17+
OperationObject,
18+
RunOn,
19+
TestEvent,
20+
Topology,
21+
},
1422
util::{
1523
assert_matches,
1624
CommandEvent,
@@ -32,12 +40,13 @@ const MAX_POOL_SIZE: u32 = 100;
3240

3341
lazy_static! {
3442
pub(crate) static ref CLIENT_OPTIONS: ClientOptions = {
35-
let uri = std::env::var("MONGODB_URI")
36-
.unwrap_or_else(|_| "mongodb://localhost:27017".to_string());
43+
let uri = DEFAULT_URI.clone();
3744
let mut options = ClientOptions::parse_without_srv_resolution(&uri).unwrap();
3845
options.max_pool_size = Some(MAX_POOL_SIZE);
3946

4047
options
4148
};
4249
pub(crate) static ref LOCK: TestLock = TestLock::new();
50+
pub(crate) static ref DEFAULT_URI: String =
51+
std::env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://localhost:27017".to_string());
4352
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"description": "example-insertOne",
3+
"schemaVersion": "1.0",
4+
"runOnRequirements": [
5+
{
6+
"minServerVersion": "2.6"
7+
}
8+
],
9+
"createEntities": [
10+
{
11+
"client": {
12+
"id": "client0",
13+
"observeEvents": [
14+
"commandStartedEvent"
15+
]
16+
}
17+
},
18+
{
19+
"database": {
20+
"id": "database0",
21+
"client": "client0",
22+
"databaseName": "test"
23+
}
24+
},
25+
{
26+
"collection": {
27+
"id": "collection0",
28+
"database": "database0",
29+
"collectionName": "coll"
30+
}
31+
}
32+
],
33+
"initialData": [
34+
{
35+
"collectionName": "coll",
36+
"databaseName": "test",
37+
"documents": [
38+
{
39+
"_id": 1
40+
}
41+
]
42+
}
43+
],
44+
"tests": [
45+
{
46+
"description": "insertOne",
47+
"operations": [
48+
{
49+
"object": "collection0",
50+
"name": "insertOne",
51+
"arguments": {
52+
"document": {
53+
"_id": 2
54+
}
55+
},
56+
"expectResult": {
57+
"insertedId": {
58+
"$$unsetOrMatches": 2
59+
}
60+
}
61+
}
62+
],
63+
"expectEvents": [
64+
{
65+
"client": "client0",
66+
"events": [
67+
{
68+
"commandStartedEvent": {
69+
"commandName": "insert",
70+
"databaseName": "test",
71+
"command": {
72+
"insert": "coll",
73+
"documents": [
74+
{
75+
"_id": 2
76+
}
77+
]
78+
}
79+
}
80+
}
81+
]
82+
}
83+
],
84+
"outcome": [
85+
{
86+
"collectionName": "coll",
87+
"databaseName": "test",
88+
"documents": [
89+
{
90+
"_id": 1
91+
},
92+
{
93+
"_id": 2
94+
}
95+
]
96+
}
97+
]
98+
}
99+
]
100+
}

0 commit comments

Comments
 (0)