Skip to content

Commit 68b9c3e

Browse files
committed
CSHARP-1416: added in the ConnectionString specification tests.
1 parent c1a8b85 commit 68b9c3e

23 files changed

+2740
-12
lines changed

src/MongoDB.Driver.Core.Tests/Core/Configuration/ConnectionStringTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
namespace MongoDB.Driver.Core.Configuration
2929
{
3030
[TestFixture]
31+
[Category("ConnectionString")]
3132
public class ConnectionStringTests
3233
{
3334
[Test]
@@ -427,7 +428,7 @@ public void When_one_set_of_readPreferenceTags_is_specified()
427428
{
428429
var subject = new ConnectionString("mongodb://localhost?readPreferenceTags=dc:east,rack:1");
429430

430-
var tagSet = new TagSet(new List<Tag>
431+
var tagSet = new TagSet(new List<Tag>
431432
{
432433
new Tag("dc", "east"),
433434
new Tag("rack", "1")
@@ -442,13 +443,13 @@ public void When_two_sets_of_readPreferenceTags_are_specified()
442443
{
443444
var subject = new ConnectionString("mongodb://localhost?readPreferenceTags=dc:east,rack:1&readPreferenceTags=dc:west,rack:2");
444445

445-
var tagSet1 = new TagSet(new List<Tag>
446+
var tagSet1 = new TagSet(new List<Tag>
446447
{
447448
new Tag("dc", "east"),
448449
new Tag("rack", "1")
449450
});
450451

451-
var tagSet2 = new TagSet(new List<Tag>
452+
var tagSet2 = new TagSet(new List<Tag>
452453
{
453454
new Tag("dc", "west"),
454455
new Tag("rack", "2")

src/MongoDB.Driver.Core.Tests/MongoDB.Driver.Core.Tests.csproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
<Compile Include="Core\Clusters\ServerSelectors\EndPointServerSelectorTests.cs" />
148148
<Compile Include="Core\Clusters\SingleServerClusterTests.cs" />
149149
<Compile Include="SetUpFixture.cs" />
150+
<Compile Include="Specifications\connection-string\TestRunner.cs" />
150151
<Compile Include="Specifications\server-discovery-and-monitoring\TestRunner.cs" />
151152
<Compile Include="Specifications\server-selection\ServerSelectionTestRunner.cs" />
152153
<Compile Include="Specifications\server-selection\RttTestRunner.cs" />
@@ -231,6 +232,22 @@
231232
<ItemGroup>
232233
<Compile Include="Core\Clusters\MultiServerClusterTests.cs" />
233234
<None Include="packages.config" />
235+
<EmbeddedResource Include="Specifications\connection-string\tests\invalid-uris.json" />
236+
<EmbeddedResource Include="Specifications\connection-string\tests\invalid-uris.yml" />
237+
<EmbeddedResource Include="Specifications\connection-string\tests\Makefile" />
238+
<EmbeddedResource Include="Specifications\connection-string\tests\README.rst" />
239+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-auth.json" />
240+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-auth.yml" />
241+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-host_identifiers.json" />
242+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-host_identifiers.yml" />
243+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-options.json" />
244+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-options.yml" />
245+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-unix_socket-absolute.json" />
246+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-unix_socket-absolute.yml" />
247+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-unix_socket-relative.json" />
248+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-unix_socket-relative.yml" />
249+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-warnings.json" />
250+
<EmbeddedResource Include="Specifications\connection-string\tests\valid-warnings.yml" />
234251
<None Include="Specifications\server-discovery-and-monitoring\tests\Makefile" />
235252
<None Include="Specifications\server-discovery-and-monitoring\tests\README.rst" />
236253
<EmbeddedResource Include="Specifications\server-discovery-and-monitoring\tests\rs\discovery.json" />
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/* Copyright 2015 MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.IO;
19+
using System.Linq;
20+
using System.Net;
21+
using System.Net.Sockets;
22+
using System.Reflection;
23+
using FluentAssertions;
24+
using MongoDB.Bson;
25+
using MongoDB.Driver.Core.Configuration;
26+
using MongoDB.Driver.Core.Misc;
27+
using NUnit.Framework;
28+
29+
namespace MongoDB.Driver.Specifications.connection_string
30+
{
31+
[TestFixture]
32+
public class TestRunner
33+
{
34+
[TestCaseSource(typeof(TestCaseFactory), "GetTestCases")]
35+
public void RunTestDefinition(BsonDocument definition)
36+
{
37+
ConnectionString connectionString = null;
38+
Exception parseException = null;
39+
try
40+
{
41+
connectionString = new ConnectionString((string)definition["uri"]);
42+
}
43+
catch (Exception ex)
44+
{
45+
parseException = ex;
46+
}
47+
48+
if (parseException == null)
49+
{
50+
AssertValid(connectionString, definition);
51+
}
52+
else
53+
{
54+
AssertInvalid(parseException, definition);
55+
}
56+
}
57+
58+
private void AssertValid(ConnectionString connectionString, BsonDocument definition)
59+
{
60+
if (!definition["valid"].ToBoolean())
61+
{
62+
Assert.Fail($"The connection string '{definition["uri"]}' should be invalid.");
63+
}
64+
65+
var hostsValue = definition["hosts"] as BsonArray;
66+
if (hostsValue != null)
67+
{
68+
var expectedEndPoints = hostsValue
69+
.Select(x => ConvertExpectedHostToEndPoint((BsonDocument)x))
70+
.ToList();
71+
72+
var missing = expectedEndPoints.Except(connectionString.Hosts, EndPointHelper.EndPointEqualityComparer);
73+
missing.Any().Should().Be(false);
74+
75+
var additions = connectionString.Hosts.Except(expectedEndPoints, EndPointHelper.EndPointEqualityComparer);
76+
additions.Any().Should().Be(false);
77+
}
78+
79+
var authValue = definition["auth"] as BsonDocument;
80+
if (authValue != null)
81+
{
82+
connectionString.DatabaseName.Should().Be(ValueToString(authValue["db"]));
83+
connectionString.Username.Should().Be(ValueToString(authValue["username"]));
84+
connectionString.Password.Should().Be(ValueToString(authValue["password"]));
85+
}
86+
}
87+
88+
private void AssertInvalid(Exception ex, BsonDocument definition)
89+
{
90+
// we will assume warnings are allowed to be errors...
91+
if (definition["valid"].ToBoolean() && !definition["warning"].ToBoolean())
92+
{
93+
throw new AssertionException($"The connection string '{definition["uri"]}' should be valid.", ex);
94+
}
95+
}
96+
97+
private string ValueToString(BsonValue value)
98+
{
99+
if (value == BsonNull.Value)
100+
{
101+
return null;
102+
}
103+
104+
return value.ToString();
105+
}
106+
107+
private EndPoint ConvertExpectedHostToEndPoint(BsonDocument expectedHost)
108+
{
109+
var port = expectedHost["port"];
110+
if (port.IsBsonNull)
111+
{
112+
port = 27017;
113+
}
114+
if (expectedHost["type"] == "ipv4" || expectedHost["type"] == "ip_literal")
115+
{
116+
return new IPEndPoint(
117+
IPAddress.Parse(ValueToString(expectedHost["host"])),
118+
port.ToInt32());
119+
}
120+
else if (expectedHost["type"] == "hostname")
121+
{
122+
return new DnsEndPoint(
123+
ValueToString(expectedHost["host"]),
124+
port.ToInt32());
125+
}
126+
else if (expectedHost["type"] == "unix")
127+
{
128+
Assert.Ignore();
129+
}
130+
131+
throw new AssertionException($"Unknown host type {expectedHost["type"]}.");
132+
}
133+
134+
private static class TestCaseFactory
135+
{
136+
private static readonly string[] _ignoredTestNames = new string[]
137+
{
138+
"invalid-uris: Missing delimiting slash between hosts and options"
139+
};
140+
141+
public static IEnumerable<ITestCaseData> GetTestCases()
142+
{
143+
const string prefix = "MongoDB.Driver.Specifications.connection_string.tests.";
144+
return Assembly
145+
.GetExecutingAssembly()
146+
.GetManifestResourceNames()
147+
.Where(path => path.StartsWith(prefix) && path.EndsWith(".json"))
148+
.SelectMany(path =>
149+
{
150+
var definition = ReadDefinition(path);
151+
var tests = (BsonArray)definition["tests"];
152+
var fullName = path.Remove(0, prefix.Length);
153+
var list = new List<TestCaseData>();
154+
foreach (BsonDocument test in tests)
155+
{
156+
var data = new TestCaseData(test);
157+
data.Categories.Add("Specifications");
158+
data.Categories.Add("ConnectionString");
159+
var testName = fullName.Remove(fullName.Length - 5) + ": " + test["description"];
160+
if (_ignoredTestNames.Contains(testName))
161+
{
162+
data = data.Ignore();
163+
}
164+
list.Add(data.SetName(testName));
165+
}
166+
return list;
167+
});
168+
}
169+
170+
private static BsonDocument ReadDefinition(string path)
171+
{
172+
using (var definitionStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path))
173+
using (var definitionStringReader = new StreamReader(definitionStream))
174+
{
175+
var definitionString = definitionStringReader.ReadToEnd();
176+
return BsonDocument.Parse(definitionString);
177+
}
178+
}
179+
}
180+
}
181+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
YAML_FILES=$(shell find . -iname '*.yml')
2+
JSON_FILES=$(patsubst %.yml,%.json,$(YAML_FILES))
3+
4+
all: $(JSON_FILES)
5+
6+
%.json : %.yml
7+
jwc yaml2json $< > $@
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
=======================
2+
Connection String Tests
3+
=======================
4+
5+
The YAML and JSON files in this directory tree are platform-independent tests
6+
that drivers can use to prove their conformance to the Connection String Spec.
7+
8+
As the spec is primarily concerned with parsing the parts of a URI, these tests
9+
do not focus on host and option validation. Where necessary, the tests use
10+
options known to be (un)supported by drivers to assert behavior such as issuing
11+
a warning on repeated option keys. As such these YAML tests are in no way a
12+
replacement for more thorough testing. However, they can provide an initial
13+
verification of your implementation.
14+
15+
Converting to JSON
16+
------------------
17+
18+
The tests are written in YAML because it is easier for humans to write and read,
19+
and because YAML includes a standard comment format. A JSONified version of each
20+
YAML file is included in this repository. Whenever you change the YAML,
21+
re-convert to JSON. One method to convert to JSON is with
22+
`jsonwidget-python <http://jsonwidget.org/wiki/Jsonwidget-python>`_::
23+
24+
pip install PyYAML urwid jsonwidget
25+
make
26+
27+
Or instead of "make"::
28+
29+
for i in `find . -iname '*.yml'`; do
30+
echo "${i%.*}"
31+
jwc yaml2json $i > ${i%.*}.json
32+
done
33+
34+
Alternatively, you can use `yamljs <https://www.npmjs.com/package/yamljs>`_::
35+
36+
npm install -g yamljs
37+
yaml2json -s -p -r .
38+
39+
Version
40+
-------
41+
42+
Files in the "specifications" repository have no version scheme. They are not
43+
tied to a MongoDB server version, and it is our intention that each
44+
specification moves from "draft" to "final" with no further versions; it is
45+
superseded by a future spec, not revised.
46+
47+
However, implementers must have stable sets of tests to target. As test files
48+
evolve they will be occasionally tagged like "uri-tests-tests-2015-07-16", until
49+
the spec is final.
50+
51+
Format
52+
------
53+
54+
Each YAML file contains an object with a single ``tests`` key. This key is an
55+
array of test case objects, each of which have the following keys:
56+
57+
- ``description``: A string describing the test.
58+
- ``uri``: A string containing the URI to be parsed.
59+
- ``valid:`` A boolean indicating if the URI should be considered valid.
60+
- ``warning:`` A boolean indicating whether URI parsing should emit a warning
61+
(independent of whether or not the URI is valid).
62+
- ``hosts``: An array of host objects, each of which have the following keys:
63+
64+
- ``type``: A string denoting the type of host. Possible values are "ipv4",
65+
"ip_literal", "hostname", and "unix". Asserting the type is *optional*.
66+
- ``host``: A string containing the parsed host.
67+
- ``port``: An integer containing the parsed port number.
68+
- ``auth``: An object containing the following keys:
69+
70+
- ``username``: A string containing the parsed username. For auth mechanisms
71+
that do not utilize a password, this may be the entire ``userinfo`` token
72+
(as discussed in `RFC 2396 <https://www.ietf.org/rfc/rfc2396.txt>`_).
73+
- ``password``: A string containing the parsed password.
74+
- ``db``: A string containing the parsed authentication database. For legacy
75+
implementations that support namespaces (databases and collections) this may
76+
be the full namespace eg: ``<db>.<coll>``
77+
- ``options``: An object containing key/value pairs for each parsed query string
78+
option.
79+
80+
If a test case includes a null value for one of these keys (e.g. ``auth: ~``,
81+
``port: ~``), no assertion is necessary. This both simplifies parsing of the
82+
test files (keys should always exist) and allows flexibility for drivers that
83+
might substitute default values *during* parsing (e.g. omitted ``port`` could be
84+
parsed as 27017).
85+
86+
The ``valid`` and ``warning`` fields are boolean in order to keep the tests
87+
flexible. We are not concerned with asserting the format of specific error or
88+
warnings messages strings.
89+
90+
Use as unit tests
91+
=================
92+
93+
Testing whether a URI is valid or not should simply be a matter of checking
94+
whether URI parsing (or MongoClient construction) raises an error or exception.
95+
Testing for emitted warnings may require more legwork (e.g. configuring a log
96+
handler and watching for output).
97+
98+
Not all drivers may be able to directly assert the hosts, auth credentials, and
99+
options. Doing so may require exposing the driver's URI parsing component.

0 commit comments

Comments
 (0)