C# Client Library
A C# Client Library for the AnalyzeRe REST API
Loading...
Searching...
No Matches
Meta_TestSuite_Utilities_Server.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.Linq;
6using System.Net;
7using System.Reflection;
8using System.Threading;
9using System.Threading.Tasks;
10
11using AnalyzeRe;
17
18#if MSTEST
19using Microsoft.VisualStudio.TestTools.UnitTesting;
20#elif NUNIT
21using NUnit.Framework;
22using TestClass = NUnit.Framework.TestFixtureAttribute;
23using TestMethod = NUnit.Framework.TestAttribute;
24using TestCategory = NUnit.Framework.CategoryAttribute;
25using AssertFailedException = NUnit.Framework.AssertionException;
26#endif
27
29{
30 [TestClass]
32 {
33 private const string Category = "Test Utilities";
34
35 #region Test Sample Injection
36 [TestMethod, TestCategory(Category), TestCategory("Randomized")]
38 {
39 // Assert that tests will not fail if multiple threads are simultaneously
40 // trying to use the same injected samples.
42 typeof(Samples).GetPublicProperties_Fast().Where(p =>
44 .OrderBy(random => Guid.NewGuid());
46 ParallelLoopResult result = Parallel.ForEach(toInject, property =>
47 {
48 // Do not attempt to POST resources that are not saveable.
49 if (property.PropertyType.GetGenericArguments()[0]
50 .IsAttributeDefinedFast<NotSaveableAttribute>())
51 return;
52 // Overkill, spin off 2 additional threads to do the same action
53 new Thread(() => TestPostAction(property, thread_failures)).Start();
54 TestPostAction(property, thread_failures);
55 new Thread(() => TestPostAction(property, thread_failures)).Start();
56 });
57 while (!result.IsCompleted)
58 Thread.Sleep(50);
59 if (thread_failures.Any())
60 Assert.Fail(thread_failures.Count + " assertions failed during " +
61 "concurrent resource injection:\n" + String.Join("\n", thread_failures));
62 }
63
64 private static void TestPostAction(PropertyInfo property, ConcurrentBag<Exception> errors)
65 {
66 try
67 {
68 Debug.WriteLine("Sample Resource \"" + property.Name +
69 "\" (type " + property.PropertyType.NiceTypeName() + ") concurrent request received.");
72 Debug.WriteLine("Sample Resource \"" + property.Name +
73 "\" (type " + property.PropertyType.NiceTypeName() + ") acquired. Invoking Post.");
75 Debug.WriteLine("Sample Resource \"" + property.Name +
76 "\" (type " + property.PropertyType.NiceTypeName() + ") Post Complete.");
78 }
79 catch (Exception ex)
80 {
81 errors.Add(ex);
82 }
83 }
84
115 [TestMethod, TestCategory(Category), TestCategory("Randomized")]
117 {
118 // Generate a random instance of each type, and re-randomize each property
121 Console.WriteLine("Note: Configured Target Analysis Profile is: " +
123 Console.WriteLine(" Configured Target Event Catalog is: " +
125 // Toggle the below constant to simplify test debugging.
126 const bool runInParallel = true;
127 // ReSharper disable once ConditionIsAlwaysTrueOrFalse
128#pragma warning disable 162
129 if (runInParallel)
130 tests.AsParallel().ForAll(RandomTypeTest);
131 else
132 tests.ToList().ForEach(RandomTypeTest);
133#pragma warning restore 162
134 }
135
136 // Helper method for the above run on each subtype discovered via reflection.
137 private void RandomTypeTest(Type subtype)
138 {
139 // Generate a random instance of this type
142 {
143 Assert.Fail("CreateRandomizedInstance did not generate a valid " +
144 $"{subtype.NiceTypeName()} instance. Result was \"{test ?? "(null)"}\"");
145 return;
146 }
147
148 string threadId = $"T{Thread.CurrentThread.ManagedThreadId} ";
149 Console.WriteLine($"{threadId}{DateTime.UtcNow:mm:ss.ff} Generated a random " +
150 $"{subtype.NiceTypeName()} with runtime type {test.GetType().NiceTypeName()}");
151 // POST should succeed, because Validation was enabled.
152 IAPIResource posted = null;
153 try
154 {
155 posted = testResource.Post();
156 }
157 catch (Exception ex)
158 {
160 apiError.RestResponse.StatusCode == HttpStatusCode.BadRequest ?
161 $"generated an invalid {subtype.NiceTypeName()} resulting in a rejected POST" :
162 $"encountered an unexpected POST error for a random {subtype.NiceTypeName()}";
163 string msg = $"{threadId}{DateTime.UtcNow:mm:ss.ff} CreateRandomizedInstance {issue}." +
164 $"\nThe posted JSON was:\n{testResource.Serialize()}" +
165 $"\nThe response error was:\n{ex}";
166 Console.WriteLine(msg);
167 // The Assert.Fail throw has been observed ending the Task before logs are flushed
168 Console.Out.Flush();
169 Assert.Fail(msg);
170 }
171
172 // Give this resource data if necessary.
173 posted = Reflection.FinalizePostedResource(posted);
174
175 // Check that every writable property can be re-randomized without issue.
177 .GetUserFacingProperties(true, true).ToList();
178 // Bonus: Let's make sure each change to the resource results in a new id.
179 HashSet<string> resourceIds = new HashSet<string> { posted.id };
181 {
182 object originalValue = prop.GetValue(testResource);
183 string strProperty = $"the {test.GetType().NiceTypeName()} property " +
184 $"{prop.PropertyType.NiceTypeName()} {prop.DeclaringType.NiceTypeName()}.{prop.Name}";
185 Console.WriteLine($"{threadId}{DateTime.UtcNow:mm:ss.ff} Randomizing {strProperty}");
186 IAPIResource modified = testResource.DeepCopy().Change(r => r.id, null);
187 object newValue;
190 {
191 Console.WriteLine($"{threadId}{DateTime.UtcNow:mm:ss.ff} RandomizePropertyValue " +
192 $"reported an error generating {strProperty}:\n{ex.Message}");
193 // TODO: Handle all cases where this throws because some properties aren't
194 // randomized with validation turned on.
195 //throw;
196 continue;
197 }
198
199 string DebugValues(object v1, object v2 = null) =>
200 $"\n Original: {Output.AutoFormat(v1)}" +
201 $"\n Modified: {Output.AutoFormat(v2)}";
202
203 // Ensure the new random value was valid (i.e. POST still succeeds)
204 try { modified = modified.Post(); }
205 catch (Exception ex)
206 {
208 apiError.RestResponse.StatusCode == HttpStatusCode.BadRequest ?
209 $"generated an invalid value for {strProperty} resulting in a rejected POST" :
210 $"encountered an unexpected POST error after randomly modifying {strProperty}";
211 string msg = $"{threadId}{DateTime.UtcNow:mm:ss.ff} RandomizePropertyValue {issue}." +
212 $"Property values are:{DebugValues(originalValue, newValue)}\n\n" +
213 $"Entire resource definitions:{DebugValues(testResource, modified)}\n";
214 Console.WriteLine($"{msg}{ex.Message}");
215 // The Assert.Fail throw has been observed ending the Task before logs are flushed
216 Console.Out.Flush();
217 Assert.Fail($"{msg}{ex}");
218 }
219
220 // Ensure the property value was changed
221 if (newValue?.Equals(originalValue) ?? originalValue == null)
222 Console.WriteLine($"{threadId}{DateTime.UtcNow:mm:ss.ff} RandomizePropertyValue " +
223 $"failed to change {strProperty} - possibly because validation rules are " +
224 $"overly restrictive.{DebugValues(originalValue, newValue)}");
225 // Check that if we did manage to change the property value, that the server
226 // returned a different id from all previous versions of this resource.
227 else if (resourceIds.Contains(modified.id))
228 Assert.Fail($"The server returned the same resource id ({modified.id}) after " +
229 $"randomly changing {strProperty}.{DebugValues(originalValue, newValue)}");
230 resourceIds.Add(modified.id);
231 Reflection.FinalizePostedResource(modified);
232 }
233 }
234 #endregion Test Sample Injection
235
236 [TestMethod, TestCategory(Category)]
237 public virtual void Test_API_Detect_BadAPITest()
238 {
240 Assert.Inconclusive("RUN_OFFLINE = true");
242 // Test that the server is identified as being unclean
244 Assert.IsFalse(serverIsClean,
245 "The test utility did not properly detect that the server is not clean.");
246 }
247
248 [TestMethod, TestCategory(Category)]
249 public virtual void Test_API_Seed_And_SeedCleanup()
250 {
252 Assert.Inconclusive("Cannot test wiping server while running tests in parallel.");
257 {
258 Assert.IsTrue(CleanUp.CheckServerIsClean(), "Server did not end up clean.");
259 }
260 }
261
262 [TestMethod, TestCategory(Category)]
264 {
265 AssertApi.ExceptionThrown(() =>
266 {
267 DateTime start = DateTime.UtcNow;
268 try
269 {
270 API.PollUntilReady(() => API.GetResourceList<Layer>(timeout: 1), PollingOptions.Default);
271 }
272 catch (APIRequestException ex)
273 {
274 Console.WriteLine(ex);
275 throw;
276 }
277
278 TimeSpan elapsed = DateTime.UtcNow - start;
279 if (elapsed.TotalMilliseconds < 10)
280 Assert.Inconclusive("This test relies on timing out before a request completes, " +
281 "but this request appears to have completed so quickly (< 10ms) " +
282 "that timeout couldn't be enforced.");
283 },
285 {
286 string messageIfFailed = "The returned error message isn't as expected:\n" + ex.Message;
287 Assert.IsTrue(ex.Message.StartsWith("The request timed out"), messageIfFailed);
288 Assert.IsTrue(ex.Message.EndsWith(
289 "If this is a large request, you may need to wait longer for a response. " +
290 "Check your API.DefaultRequestTimeout or set a timeout directly in your API call."),
292 });
293 }
294 }
295}
Create a test class that takes care of setting the server URL and cleaning up after each unit test.
Exposes sample resource objects, with built-in methods for injecting dependencies.
Definition Samples.cs:14
static List< Type > GetAPIResourceTypes()
A list of base "production" resource types on the server, (e.g. each with their own collection) used ...
IInjectableResource< BinomialDistribution > Distribution_BinomialDistribution
static void IsValidUUID(string toCheck)
Assert that the string is a valid UUID.
Definition AssertApi.cs:644
Utilities for cleaning up the server.
Definition CleanUp.cs:19
static bool CheckServerIsClean(bool ignoreExceptions=false)
Check all known server endpoints and determine whether there are any resources on the server.
Definition CleanUp.cs:23
Retrieve settings from environment variables if they exist, or the project settings file otherwise.
static bool RUN_OFFLINE
Controls whether tests that normally require a connection to the server should be allowed to try to r...
static bool ROOT_DELETE_SUPPORTED
Indicates whether the server currently supports the DELETE method on the server root to wipe out all ...
static bool RESOURCE_DELETE_SUPPORTED
Indicates whether the server currently supports the DELETE method on resources.
static bool EXECUTING_IN_PARALLEL
If true, tests are being run in parallel, so certain assumptions about the state of the server after ...
An exception throw when a random generation routine fails to produce a POSTable resource.
A collection of filthy hacks to populate some fields of APIResources objects of any type.
Definition Reflection.cs:41
static IEnumerable< Type > GetAllInstantiableSubtypes(Type type)
Creates a list of types that can be derived from a given type.
AnalysisProfile Target_AnalysisProfile
The default AnalysisProfile to use for various validation assurances.
Definition Reflection.cs:72
object ChangePropertyValueRandomly(IAPIType obj, PropertyInfo prop, int max_attempts=Max_ReRandomize_Attempts, RecursionContext parentRecursionInfo=null)
Randomly change the specified property to some different value.
object CreateRandomizedInstance(Type desiredType, RecursionContext parentRecursionInfo=null)
Generates a new randomized object of the specified type.
A collection of scripts that are sometimes used to batch seed or modify data on a test server.
Definition Scripts.cs:14
A custom exception class that includes the RestSharp.IRestResponse that generated the exception,...
string id
The resource's unique identifier. It will be used in the request URL when requesting the resource fro...
Describes a collection of resources which can be listed.
API methods / requests made available to the user.
static ICollectionResponse< IAPIResource > GetResourceList(Type resourceType, IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int? timeout=null)
Get a collection of resources from the server.
List< IReference< EventCatalog > > event_catalogs
The EventCatalogs to use during this simulation.
Indicates that while the current APIResource-derived class can be constructed and potentially inlined...
Abstract representation of a distribution, used for parametric loss sets.
Abstract representation of a layer. This resource type cannot be instantiated instead derived resourc...
Definition Layer.cs:12
Determines the behaviour of the API when automatically retrying a request whose result is not yet rea...
static PollingOptions Default
The default PollingArguments used when none are specified.
Extends the TimeoutException class to contain the IRestResponse that timed out.
T Posted
The posted resource, ready to be referenced.
Interface for Base class used by all resources.
string id
The resource's unique identifier. It will be used in the request URL when requesting the resource fro...