C# Client Library
A C# Client Library for the AnalyzeRe REST API
Loading...
Searching...
No Matches
Reflection.cs
Go to the documentation of this file.
1using System;
2using System.Collections;
3using System.Collections.Concurrent;
4using System.Collections.Generic;
5using System.Diagnostics;
6using System.Linq;
7using System.Linq.Expressions;
8using System.Reflection;
9using System.Threading;
10
11using AnalyzeRe;
24
25#if MSTEST
26using Microsoft.VisualStudio.TestTools.UnitTesting;
27#elif NUNIT
28using NUnit.Framework;
29using TestClass = NUnit.Framework.TestFixtureAttribute;
30using TestMethod = NUnit.Framework.TestAttribute;
31using TestCategory = NUnit.Framework.CategoryAttribute;
32#endif
33
35{
39 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
40 public class Reflection
41 {
42 #region Properties
44 private const int Recursions_Before_Calming_Down = 8;
45
48 private const int Max_ReRandomize_Attempts = 20;
49
51 private const int Max_Recursive_Calls = 16;
52
54 private const double Min_Random = -1E9;
55
57 private const double Max_Random = 1E9;
58
62 private readonly ThreadLocal<Random> thread_safe_random;
63
65 private Random _random => thread_safe_random.Value;
66
68 private readonly Samples _samples;
69
72 {
73 get => !Validation_Enabled ? null : _targetAnalysisProfile ??
74 (_targetAnalysisProfile = _samples.AnalysisProfile.Posted);
75 set => _targetAnalysisProfile = value;
76 }
77 private AnalysisProfile _targetAnalysisProfile;
78
83 public bool Validation_Enabled { get; set; } = true;
84 #endregion Properties
85
96 public Reflection(Samples samplesInstance = null, int? seedForRandom = null)
97 {
98 _samples = samplesInstance ?? new Samples();
99
100 int seed = seedForRandom ?? Environment.TickCount;
101 Console.WriteLine($"Seed for tests is: {seed} - but note that if tests are being run " +
102 "concurrently, this seed cannot be used to reproduce the same results. " +
103 "For a seed to guarantee the same results every time, tests must be run one at a time, " +
104 "or with parallelism disabled on the test runner itself (MSTest or NUnit).");
105 thread_safe_random = new ThreadLocal<Random>(() => new Random(seed));
106 }
107
118 public class RecursionContext
119 {
121 public Type Type { get; }
123 public int Depth { get; }
125 public RecursionContext Prior { get; }
126
128 {
130 Depth = depth;
131 Prior = prior;
132 }
133
135
137
140
143 condition(this) || HasParent(condition);
144
148 {
149 if (condition == null) return Prior != null;
150 return Prior?.MatchesCondition(condition) ?? false;
151 }
152
155 [Conditional("DEBUG")]
156 public void LogIndented(string log)
157 {
158 string indent = new string(' ', Depth * 2);
159 Debug.WriteLine(indent +
160 log.Replace(Environment.NewLine, Environment.NewLine + indent));
161 }
162
163 public override string ToString() =>
164 (Prior == null ? "Recursion Stack: " : $"{Prior} > ") + Type.NiceTypeName();
165 }
166
171
180 {
181 if (desiredType == null)
182 throw new NullReferenceException("No desiredType parameter specified.");
184 // If this type is a nullable, unwrap to the underlying type immediately.
185 Type T = Nullable.GetUnderlyingType(desiredType) ?? desiredType;
186#if DEBUG
187 // Keep a log of each time we are about to randomly generate a non-trivial instance.
188 if (!(T.IsPrimitive || T.IsValueType || T == typeof(object) || T == typeof(string)))
189 currentRecursion.LogIndented($"Create randomized instance ({currentRecursion.Depth}): " +
190 $"{desiredType.NiceTypeName()}");
191#endif
192 // Different random generation behaviours based on the type. Trivial types first.
193 // Random String
194 if (T == typeof(string))
195 return Output.RandomString(1, 100, _random);
196 // Random Boolean
197 else if (T == typeof(bool))
198 return _random.Next(2) == 0;
199 // Randomized Date
200 else if (T == typeof(DateTime))
201 return _samples.SimulationStartDate.AddDays(_random.Next(-365, 366));
202 // Random int, long, etc...
203 else if (T.IsIntegerType())
204 {
205 // Get the minimum value allowed by the type (e.g. 0 for unsigned types)
206 long minSupported = Convert.ToInt64(T.GetField("MinValue").GetValue(null));
207 // The minimum integer to generate is the most restrictive of the minimum
208 // value supported by the type, and the configured minimum random number.
209 int min = (int)Math.Max((long)Min_Random, minSupported);
210 int random = _random.Next(min, (int)Max_Random);
211 return Convert.ChangeType(random, T);
212 }
213 // Random double, decimal, etc...
214 else if (T.IsNumeric())
215 {
216 double random = _random.NextDouble() * (Max_Random - Min_Random) + Min_Random;
217 return Convert.ChangeType(random, T);
218 }
219 // Random enumeration value
220 else if (T.IsEnum)
221 return T.GetEnumValues().GetValue(_random.Next(T.GetEnumValues().Length));
222 // Random arbitrary object (e.g. for metadata values) can be just about any primitive.
223 else if (T == typeof(object))
224 return CreateRandomizedInstance(_random.Next(5) == 0 ? typeof(string) :
225 _random.Next(4) == 0 ? typeof(bool) :
226 _random.Next(3) == 0 ? typeof(DateTime) :
227 _random.Next(2) == 0 ? typeof(int) : typeof(double), currentRecursion);
228
229 // The following types are less trivial, but still system types
230 // Random dictionary
231 else if (typeof(IDictionary).IsAssignableFrom(T))
232 {
233 IDictionary dict = (IDictionary)Activator.CreateInstance(T);
234 object key = CreateRandomizedInstance(T.GetGenericArguments()[0], currentRecursion);
235 if (key is string strKey)
236 {
237 // String dictionary keys cannot contain . or $ characters.
238 strKey = strKey.Replace(".", "").Replace("$", "");
239 // Bug ARE-7244: If the key is a string, but can be parsed as a number, it can cause errors
240 if (Double.TryParse(strKey, out _)) strKey = "Str" + strKey;
241 key = strKey;
242 }
243 // Insert a single item of the target type using the above key.
244 dict.Add(key, CreateRandomizedInstance(T.GetGenericArguments()[1], currentRecursion));
245 return dict;
246 }
247 // Random HashSet
248 else if (T.IsGenericType && T.GetGenericTypeDefinition() == typeof(HashSet<>))
249 {
250 // Create a randomized list of the correct type, then insert them into a HashSet
251 IList randList = CreateRandomizedList(T.GetGenericArguments()[0], currentRecursion);
252 object hashSetResult = Activator.CreateInstance(T);
253 // Have to use get and use the strongly typed Add method of the generic HashSet
254 MethodInfo addMethod = T.GetMethod("Add");
255 foreach (object toAdd in randList.Cast<object>())
256 addMethod.Invoke(hashSetResult, new[] { toAdd });
257 return hashSetResult;
258 }
259 // Random List of items.
260 else if (typeof(IList).IsAssignableFrom(T))
261 {
263 IList randomList = CreateRandomizedList(itemsType, currentRecursion);
264 // If the generated list can be assigned directly to the target type, return it directly.
265 if (T.IsInstanceOfType(randomList))
266 return randomList;
267 // Otherwise, create a new instance of the same type, and add all items
268 if (T.IsAbstract || T.IsInterface)
269 throw new ArgumentException($"Unsure how to construct a new {T.NiceTypeName()} " +
270 "because it is an abstract class or interface.");
271 try
272 {
273 // As far as I know, all types that implement IList (including arrays) can be
274 // constructed from an array of objects of the list type. If this fails for
275 // certain list types, we may need to add a check above and construct differently.
276 return (IList)Activator.CreateInstance(T, randomList.Cast<object>().ToArray());
277 }
278 catch (Exception ex)
279 {
280 throw new ArgumentException($"An attempt to construct a new {T.NiceTypeName()} " +
281 $"from an array of {itemsType.NiceTypeName()} failed: {ex.Message}", ex);
282 }
283 }
284
285 // Random APIType or APIResource (i.e. custom types)
287 {
288 // Defer to a specialized method for randomly generating custom API type instances
289 return CreateRandomizedAPIType(T, currentRecursion);
290 }
291
292 // Unknown type, can't be randomly generated
293 string message = $"No rules for generating a random value of type: \"{T.NiceTypeName()}\".";
294 currentRecursion.LogIndented(message);
295 // Only fail if this is a top-level instance generation. Otherwise, try returning null
296 if (currentRecursion.Depth == 0)
297 Assert.Fail(message);
298 return T.IsValueType ? Activator.CreateInstance(T) : null;
299 }
300
301 #region Type Specific Random Generation Helpers
304 bool allowReuseOfExisting = true) where T : IAPIResource =>
305 (IReference<T>)CreateRandomizedReference(typeof(IReference<T>), currentRecursion, allowReuseOfExisting);
306
315 private IReference<IAPIResource> CreateRandomizedReference(Type referenceType,
316 RecursionContext currentRecursion, bool allowReuseOfExisting = true)
317 {
318 if (referenceType == null)
321 throw new ArgumentException("This function requires a generic reference type. " +
322 $"The type given was {referenceType.NiceTypeName()}");
323
324 // Extract the resource type we wish to create a reference to (we call this T for compactness)
325 Type T = referenceType.GetGenericArguments()[0];
326
327 // If we do not need a valid reference, return a reference to a random GUID
329 {
330 Type runtimeType = typeof(Reference<>).MakeGenericTypeFast(T);
331 ConstructorInfo referenceConstructor = runtimeType.GetConstructor(new[] { typeof(string) });
332 if (referenceConstructor == null)
333 throw new Exception($"Missing Reference Constructor {runtimeType.NiceTypeName()}(string)");
334 // Construct the new reference using the deserialized data.
336 new object[] { Guid.NewGuid().ToString() });
337 }
338 if (typeof(ILayerSource) == T)
339 throw new Exception($"CreateRandomizedReference called for a type {referenceType.NiceTypeName()}, " +
340 "but this routine should not being used for IReference<ILayerSource> properties. " +
341 "All those properties should be using GenerateNestedLayerSourceReference.");
342
343 // To avoid spending too much time posting random new resources, if we need a reference
344 // to an APIResource, we will sometimes opt to search the server for an existing
345 // resource rather than POST a new one and then reference it.
346 // TODO: This is almost the same as the logic used to decide whether to generate
347 // a new IAPIResource or find an existing one in CreateRandomizedAPIType.
348 // consider consolidating this logic.
349 bool reuse_existing;
350 // Favour generating a new resource from scratch if we're still generating the first
351 // level of properties - since most tests want root level properties randomized.
352 if (currentRecursion.Depth < 2 || !allowReuseOfExisting)
353 reuse_existing = false;
354 // Conversely, always try to use an existing resource if we've recursed quite a lot.
355 else if (currentRecursion.Depth >= Recursions_Before_Calming_Down)
356 reuse_existing = true;
357 // Otherwise there's a 1/2 chance we try getting a reference to an existing one first.
358 // If it has a data endpoint, make it a 3/4 chance, to avoid costly file uploads.
359 else
360 reuse_existing = 0 < _random.Next(
362
363 // If the above logic decided we will be re-using an existing resource, do so
364 if (reuse_existing)
365 {
366 currentRecursion.LogIndented("Opted to find an existing resource of type " +
367 $"{T.NiceTypeName()} on thread {Thread.CurrentThread.ManagedThreadId}");
369 // We should return a copy of the existing resource, in case the caller intends to modify it.
370 if (existing != null)
371 return existing;
372 currentRecursion.LogIndented($"No satisfactory existing {T.NiceTypeName()} found. " +
373 "Reverting to generating a new resource.");
374 }
375
376 // Create a new (unposted) randomized instance of the specified type.
378
379 // If successful, post our new randomly generated object so that it is valid to reference.
380 if (res != null)
381 {
382 string reqDescription = $"Post a randomly generated {T.NiceTypeName()}";
383 BeginRequest(currentRecursion, reqDescription);
384
385 try { res = res.Post(); }
386 catch (APIRequestException ex)
387 {
388 string error = $"Failed to generate a valid {T.NiceTypeName()}. " +
389 $"Post of the resource to reference failed with: {ex.Message}" +
390 $"\nStructure looks like:\n{res.Serialize()}";
391 Console.WriteLine(error);
392 EndRequest(reqDescription, error);
393 throw;
394 }
395 // If this resource has a data endpoint, we must upload something against it to finalize it.
397
398 EndRequest(reqDescription);
399
400 IReference<IAPIResource> asReference = res.ToReference(true);
402 return asReference;
403 }
404
405 // If we've recursed too deeply or otherwise failed to create a new reference to an
406 // allowed type, return a null reference and hope it doesn't cause any problems upstream.
407 currentRecursion.LogIndented($"Failed to generate a valid {T.NiceTypeName()}. Returning null.");
408 return null;
409 }
410
416 private List<T> CreateRandomizedList<T>(RecursionContext currentRecursion) =>
417 CreateRandomizedList(typeof(T), currentRecursion).Cast<T>().ToList();
418
424 private IList CreateRandomizedList(Type contentType, RecursionContext currentRecursion)
425 {
426 if (contentType == null) throw new ArgumentNullException(nameof(contentType));
427
428 // Otherwise, create a new list and insert one or more random items in it.
429 IList result = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericTypeFast(contentType));
430 // See how many instantiable types match the desired type.
431 // (If generic type is object, we can't get instantiable sub-types)
432 List<Type> subtypes = typeof(IAPIType).IsAssignableFrom(contentType) ?
434 // If we haven't recursed much, try to put one of every possible type of item in the list.
435 if (currentRecursion.Depth < 2 && subtypes.Count > 1)
436 {
437 currentRecursion.LogIndented("Recursion Level is low, generating a reference to " +
438 $"every instantiable subtype of {contentType.NiceTypeName()}.");
440 .Where(x => x != null).ToList().ForEach(x =>
441 {
442 // If this is a reference list, don't add the same reference to the list twice
443 // (can happen randomly when two types share a common instantiable base.)
444 bool isDuplicate = (typeof(IReference).IsAssignableFrom(contentType) &&
445 result.Cast<IReference>().Any(r => r.ref_id == ((IReference)x).ref_id));
446 if (!isDuplicate)
447 result.Add(x);
448 });
449 }
450 // Otherwise, randomly pick just one item to put in the list.
451 // (If we reached the max number of recursions, just return the empty list)
452 else if (currentRecursion.Depth < Max_Recursive_Calls)
453 {
455 if (toAdd != null)
456 result.Add(toAdd);
457 }
458 return result;
459 }
460
466 private T CreateRandomizedAPIType<T>(RecursionContext currentRecursion) where T : IAPIType =>
467 (T)CreateRandomizedAPIType(typeof(T), currentRecursion);
468
475 private IAPIType CreateRandomizedAPIType(Type T, RecursionContext currentRecursion)
476 {
477 // Try to create a new random object of this type.
478 try
479 {
480 if (T == null || !typeof(IAPIType).IsAssignableFrom(T))
481 throw new ArgumentException($"The desired type {T.NiceTypeName()} must derive from IAPIType.");
482
483 // Avoid recursing too deeply. Try returning null (hopefully it isn't for a required field)
485 if (recursionLevel > Max_Recursive_Calls)
486 {
487 currentRecursion.LogIndented($"Recursion level ({recursionLevel}) is too high " +
488 $"({Max_Recursive_Calls}) to generate a new resource. Returning a null object.");
489 return null;
490 }
491
492 // Immutable types and types without a default constructor must be handled differently.
494 return CreateRandomizedReference(T, currentRecursion);
496 return CreateRandomizedPerspective(currentRecursion);
497
498 // If we're nearing our maximum recursion level try finding an existing resource
499 // Rather than randomly generating a new one.
500 if (recursionLevel > Recursions_Before_Calming_Down && typeof(IAPIResource).IsAssignableFrom(T))
501 {
502 currentRecursion.LogIndented($"Recursion level ({recursionLevel}) is getting high " +
503 $"(>{Recursions_Before_Calming_Down}) - looking for existing resources " +
504 $"of type {T.NiceTypeName()}");
506 // We should clone the resource in case the caller intends to modify the returned object.
507 if (existing != null)
508 return Resolve(existing).DeepCopy();
509 // If that failed for whatever reason, revert to generating a new random resource.
510 }
511
512 // If T is abstract base class or an interface for a custom type,
513 // we must pick a random instantiable sub-type to generate a new instance of.
514 Type instantiableType = T;
515 RecursionContext nextRecursion = currentRecursion;
516 if (T.IsInterface || T.IsAbstract)
517 {
519 if (instanceTypes.Count == 1)
521 // If there's more than one compatible instantiable type, pick a random one
522 else
523 {
524 instantiableType = instanceTypes.OrderBy(t => _random.NextDouble()).First();
525 nextRecursion = new RecursionContext(instantiableType, currentRecursion);
526 nextRecursion.LogIndented("Elected to generate an instance of the " +
527 $"instantiable derived type: {instantiableType.NiceTypeName()}");
528 }
529 }
530
531 // Otherwise, create a new instance of the requested type and randomize its properties.
532 if (instantiableType.GetConstructor(Type.EmptyTypes) == null)
533 throw new PropertyGenerationException("Cannot create an instance of the type " +
534 $"{instantiableType.NiceTypeName()} because it has no empty constructor.");
536 return GenerateRandomAPITypeProperties(newInstance, nextRecursion);
537 }
538 catch (PropertyGenerationException e)
539 {
540 currentRecursion.LogIndented(e.Message + " Returning null object.");
541 // If this was a recursive call generating a sub-resource, try just returning null,
542 // otherwise, re-throw the exception.
543 if (currentRecursion.Depth > 0)
544 return null;
545 throw;
546 }
547 }
548
550 private Perspective CreateRandomizedPerspective(RecursionContext currentRecursion)
551 {
552 // Chance to use a random pre-defined perspective instance
553 if (_random.Next(2) == 0)
554 {
556 return perspectives[_random.Next(perspectives.Length)];
557 }
558 // Generate a perspective from a random set of perspective values that are arbitrarily combinable
559 List<Perspective.Base> values = TestSuite_Perspective.CombinablePerspectiveValues
560 .Where(p => _random.Next(2) == 0).ToList(); // 50% chance of including each perspective value
561 // Ensure there is at least one
562 if (values.Count == 0) values.Add(
564 return Perspective.FromEnums(values);
565 }
566
574 {
575 // Special case, it's unsafe to randomly pick these from existing resources,
576 // as they will almost always result in an invalid resource for posting.
577 // We need to use specifically the resources compatible with Target_AnalysisProfile
579 {
589 _random.Next(Target_AnalysisProfile.loss_filters.Count)];
591 {
592 currentRecursion.LogIndented($"Opted to reuse the {T.NiceTypeName()} " +
593 $"referenced by the target analysis profile ({Target_AnalysisProfile.id})");
596 }
597
598 // Data file references are a special case. To be valid, we should pick a reference
599 // to a data file used on a valid resource of the same type.
600 if (T == typeof(DataFile) && currentRecursion.Type != null &&
602 {
603 currentRecursion.LogIndented("Opted to look for an existing " +
604 $"{currentRecursion.Type.NiceTypeName()} whose data file we can copy)");
607 }
608 }
609
610 // See if the requested type matches any previously retrieved or created resources
611 // Get the set of cached resources for this type (this enumerable should be returned
612 // in a deterministic order) then randomize the order in some new seeded way.
614 CachedResourcesOfType(T).OrderBy(random => _random.NextDouble());
615 // Filter references to ensure we only look at ones compatible with the current analysis profile.
618 // If our recursion context has imposed restrictions on what types of layers can be
619 // nested within, ensure there are no forbidden layer types in the new reference.
620 if (typeof(ILayerSource).IsAssignableFrom(T) && GetLayerSourceRestrictions(currentRecursion)
622 {
625 source => restrictedTypes.Contains(GetLayerDefinition(source).GetType())));
626 }
627
628 // Select the first random reference that passes validation (if applicable)
630 if (selectedForReuse != null)
631 {
632 string description = !selectedForReuse.resolved ? null :
633 $"description: \"{(selectedForReuse.GetValue() as IStoredAPIResource)?.description}\" ";
634 currentRecursion.LogIndented($"Opted to reuse a previously found/generated " +
635 $"{selectedForReuse.GetType().NiceTypeName()} from the recursion context " +
636 $"({description}id: {selectedForReuse.ref_id})");
637 return selectedForReuse;
638 }
639
640 // ResourceIsValidForAnalysisProfile does not protect against there being existing resources
641 // on the server that violate some of the trickier validation criteria that RandomizePropertyValue
642 // checks for. As such, we've disabled re-using random resources on the server.
644 //string request = $"Search server for existing {T.NiceTypeName()}";
645 //BeginRequest(currentRecursion, request);
646 //IReference<IAPIResource> existing = SearchServerForRandomExistingResource(T, currentRecursion);
647 //EndRequest(request, existing == null ? "(None Found)" : $"Success: using {existing.ref_id}");
650 //if (existing != null)
651 // return existing;
652
653 // If neither of the above approaches worked, check to see if one of our standard
654 // injectable resources used throughout the test framework can be used.
655 // Assume it cannot if validation is enabled and the default sample analysis profile
656 // doesn't match the current target analysis profile.
657 if ((Target_AnalysisProfile.id == _samples.AnalysisProfile.Posted.id ||
659 {
660 currentRecursion.LogIndented($"Opted to use a standard sample {T.NiceTypeName()}");
661 return sample;
662 }
663
664 // Return null if no resources we found satisfied the list.
665 currentRecursion.LogIndented($"Could not find any existing {T.NiceTypeName()} to reference.");
666 return null;
667 }
668
672 {
673 HashSet<Type> unpostableTypes = GetUnsupportedTypes();
675 // Only look for properties that are IInjectableResource of a usable type.
676 .Where(p => p.PropertyType.IsSubclassOfRawGeneric(typeof(IInjectableResource<>)) &&
677 p.PropertyType.GetGenericArguments()[0] is Type sampleType &&
678 T.IsAssignableFrom(sampleType) && !unpostableTypes.Contains(sampleType))
679 .OrderBy(random => _random.NextDouble());
682
683 // Return the first (after random ordering) eligible sample (or null).
684 IReference<IAPIResource> matchingSample = eligibleSamples.FirstOrDefault()?.AsReference;
685 if (matchingSample != null)
687 return matchingSample;
688 }
689
690 #region Cached Existing Resources
705 private uint _trackInsertionOrder = 0;
706
715 private readonly ConcurrentDictionary<Type,
717 _knownResourcesByType = new ConcurrentDictionary<Type,
719
722 CacheResource(resource.GetType(), resource.ToReference(true));
723
726 // If the resource stored in this reference is more strongly-typed than the reference container
727 // itself, cache it as a strongly-typed reference, which may have more re-use cases.
728 reference.ResourceType == typeof(T) ? CacheResource(typeof(T), reference) :
729 // A resolved reference's ResourceType is the resolved value's runtime type
730 reference.resolved ? CacheResource(reference.GetValue()) :
731 // Otherwise, it represents the runtime type of the reference container
732 CacheResource(reference.ResourceType, reference);
733
735 {
736 if (reference.ref_id == null)
737 throw new ArgumentException("Cannot cache resources that have not been posted.");
740 _knownResourcesByType.GetOrAdd(resourceType, t =>
742 // Add to our cache. If a duplicate is already cached in there, return it instead
743 cached = dictForType.GetOrAdd(cached, c => Tuple.Create(_trackInsertionOrder++, c)).Item2;
745 throw new InvalidCastException($"The {reference.GetType().NiceTypeName()} was " +
746 $"previously cached as a more weakly typed {cached.GetType().NiceTypeName()} " +
747 "which is preventing the cached instance from being reused now.");
748 }
749
752 private IEnumerable<IReference<IAPIResource>> CachedResourcesOfType(Type T) =>
753 // First, get all cache dictionaries compatible with the requested type
754 _knownResourcesByType.Where(kvp => kvp.Key == T || T.IsAssignableFrom(kvp.Key))
755 // Ensure individual dictionaries are returned in a deterministic order
756 .OrderBy(typeDictPair => typeDictPair.Key.GetHashCode())
757 // Combine the items cached in all compatible dictionaries
758 .SelectMany(typeDictPair => typeDictPair.Value
759 // Order the elements in each dictionary based on the insertion order
760 // values (Item1), but return only the cached items themselves (Item2).
761 .Values.OrderBy(t => t.Item1).Select(t => t.Item2));
762
766 {
768 throw new Exception("Resolve should not be used when reflection class " +
769 "is configured for offline resource generation.");
770 if (reference == null) return null;
771 if (reference.ref_id == null)
772 return reference.resolved ? reference.GetValue() :
773 throw new ArgumentException("Cannot resolve references with no id");
774 // Ensure that anyone resolving a reference to this id is sharing the same resolved
775 // instance, so that once the value is resolved once, it's never retrieved again.
776 return CacheResource(reference).GetValue();
777 }
778 #endregion Cached Existing Resources
779
782 {
786 else if (posted is IStoredAPIResource_WithStatus asWithStatus && !asWithStatus.status.IsProcessingComplete())
787 finalized = (T)asWithStatus.PollUntilReady(Samples.DataPollingOptions).Get();
788 // Only cache resources that have no status, or that completed processing succeffully.
790 withStatus.status == TaskStatus.Success))
791 {
793 }
794 return finalized;
795 }
796
800 {
801 try
802 {
803 // If the resource has already been supplied a data file reference,
804 // no additional upload necessary, just poll until its data is ready.
805 if (resource.data_file != null)
806 {
807 Debug.WriteLine("UploadResourceData was give a resource that already has a data_file " +
808 $"reference ({resource.data_file.ref_id}). Polling until ready.");
809 return resource.PollUntilReady(Samples.DataPollingOptions);
810 }
811
812 // Otherwise, get the data to upload
813 string dataToUpload = null;
816 else if (resource is ELTLossSet)
818 else if (resource is YLTLossSet)
820 else if (resource is YELTLossSet)
821 {
822#pragma warning disable CS0618 // TODO: Remove when binary code is deprecated
825#pragma warning restore CS0618
827 }
828 else if (resource is EventCatalog)
838
839 if (dataToUpload == null)
840 Assert.Fail("Reflection-based data upload error: Not sure what sort of " +
841 $"data to upload for this resources of type: {resource.GetType()}");
842
843 resource.data.UploadString(dataToUpload, Samples.UploadParams);
844 return resource.PollUntilReady(Samples.DataPollingOptions);
845 }
846 catch (Exception ex)
847 {
848 // Log more information so we can debug this error
849 string error = "Upload data failed for resource of type " +
850 $"{resource.GetType().NiceTypeName()} failed: {ex.Message}" +
851 $"\nStructure looks like:\n{resource.Serialize()}";
852 Console.WriteLine($"Error: {error}");
853 throw new APIRequestException(error, ex);
854 }
855 }
856
857 // These properties must be assigned first because other properties are derived from them.
858 // Larger number is higher priority
859 private static readonly Dictionary<string, int> _propertyPriorities = new Dictionary<string, int>
860 {
866 { nameof(Nested.sink), 1 }
867 };
868
874 private IAPIType GenerateRandomAPITypeProperties(IAPIType result, RecursionContext currentRecursion)
875 {
876 Type T = result.GetType();
877 // Loop over the properties of the object we've instantiated and give them random values.
878 // These properties must be assigned first because other properties are derived from them.
879 foreach (PropertyInfo prop in T.GetUserFacingProperties(true, true).OrderByDescending(
880 p => _propertyPriorities.TryGetValue(p.Name, out int propPriority) ? propPriority : 0))
881 {
882 if (!prop.CanWrite)
883 throw new Exception("GetUserFacingProperties returned a read-only property " +
884 "even though only writable properties were requested. Property was: " +
885 $"{T.NiceTypeName()} property {prop.PropertyType.NiceTypeName()} " +
886 $"{prop.DeclaringType.NiceTypeName()}.{prop.Name}");
887 // Users can either user the data property or the data upload endpoint.
888 // We will be using the latter, so skip generating a property for this value.
889 if (IsProperty<IAPIResource_WithDataEndpoint>(prop, r => r.data_file))
890 continue;
891 int max_attempts = currentRecursion.Depth >= Recursions_Before_Calming_Down ? 1
892 : Max_ReRandomize_Attempts;
893 string last_error = null;
894 object propertyValue = LimitAttempts(() =>
895 {
896 try
897 {
898 return RandomizePropertyValue(result, prop, currentRecursion);
899 }
900 catch (PropertyGenerationException ex)
901 {
902 last_error = ex.Message;
903 return ex;
904 }
905 }, o => !(o is PropertyGenerationException),
906 () => "RandomizePropertyValue method failed: " + last_error,
908 prop.SetValue(result, propertyValue);
909 }
910 return result;
911 }
912
921 internal object RandomizePropertyValue(IAPIType obj, PropertyInfo prop, RecursionContext currentRecursion)
922 {
923 if (obj == null && Validation_Enabled)
925 "An object instance is required for several validation checks.");
926 object propertyValue;
927 // All data_file references should be left null, we will upload data manually.
929 throw new ArgumentException("Cannot reliably generate data_file references. " +
930 "The caller should make sure they are handling this property appropriately.");
931 // TODO: In certain conditions the currency needs to be locked down
932 // e.g. all exchange rate tables must have a base currency of USD to match the data?
933 //else if (IsProperty<ExchangeRateTable>(prop, s => s.base_currency))
934 //{
935 // propertyValue = Validation_Enabled ? "USD" : GetRandomCurrency();
936 //}
937 else if (prop.Name.Contains("currency"))
938 {
940 }
941 // The configured "Target_AnalysisProfile" dictates what analysis profile must be used
942 else if (Validation_Enabled && IsProperty<IAPIAnalysis>(prop, p => p.analysis_profile))
943 {
945 }
946 // The configured "Target_AnalysisProfile" dictates what event_catalog must be used
947 else if (Validation_Enabled && (
948 IsProperty<ILossSet_WithEventCatalog>(prop, p => p.event_catalogs) ||
949 IsProperty<Simulation>(prop, p => p.event_catalogs) ||
950 IsProperty<AnalysisProfile>(prop, p => p.event_catalogs)))
951 {
953 }
954 // StaticSimulation and pre-simulated loss sets' 'trial_count' must match the uploaded data.
955 else if (Validation_Enabled && (IsProperty<StaticSimulation>(prop, s => s.trial_count) ||
956 IsProperty<ILossSet_Simulated>(prop, a => a.trial_count)))
957 {
958 // In this case we know the trial count of all sample data for testing is 10 trials,
959 // but just so we can mix it up a bit - the server won't complain if the stated
960 // trial count is larger than what's in the data. e.g. the data can be sparse.
961
962 // The exception is if this Simulation belongs to a QCLSLossSet - in that case, the trial
963 // count of the simulation must match the target analysis profile.
964 if (currentRecursion.MatchesCondition(r => r.Type == typeof(QCLSLossSet)))
966 throw new ArgumentException("Expected the target analysis profile to have a static simulation.");
967 else
968 propertyValue = _random.Next(10, 20);
969 }
970 // In order to use the above rule, we have to ensure that QCLSLossSet's static simulation is not
971 // picked at random from existing resources (a possibility if using the default generation behaviour)
972 // Also provide a chance that it will simply re-use the target analysis profile's simulation.
974 {
975 propertyValue = _random.NextDouble() < 0.5 ? Target_AnalysisProfile.simulation :
976 CreateRandomizedReference(prop.PropertyType,
977 new RecursionContext(prop.PropertyType, currentRecursion), allowReuseOfExisting: false);
978 }
979 // If generating a Filter LayerView requires we use filters from the analysis profile.
980 else if (Validation_Enabled && IsProperty<Filter>(prop, p => p.filters))
981 {
982 // Hack: Again, use the known valid analysis profile which is going to be
983 // used for all randomly generated view objects.
985 Target_AnalysisProfile.loss_filters.OrderBy(t => _random.NextDouble())
986 .Take(_random.Next(Target_AnalysisProfile.loss_filters.Count)));
987 }
988 // FixedDatePayment requires as at least many payment dates as payments
989 else if (Validation_Enabled && IsProperty<FixedDatePayment>(prop, p => p.payment_dates))
990 {
991 // If payments aren't set yet, randomly generate between 1 and 10 dates
992 // otherwise, ensure we don't generate fewer than there are payments.
993 int payment_count = ((FixedDatePayment)obj)?.payments?.Count ?? 1;
994 List<DateTime> dates = Enumerable.Range(0, _random.Next(payment_count, 11))
995 // Ensure all payment dates are well after the end of the simulation year,
996 // to avoid runtime errors about insufficient payments after the last loss.
997 .Select(_ => _samples.SimulationStartDate.AddYears(2).AddDays(_random.Next(366)))
998 .OrderBy(date => date).ToList();
999 // Below is not necessary so long as sequences are predictable.
1004 //DateTime UTCLastDay = new DateTime(9999, 12, 31, 0, 0, 0, 0, DateTimeKind.Utc);
1005 //dates.AddRange(Enumerable.Repeat(UTCLastDay, payment_count));
1007 }
1008 else if (Validation_Enabled && IsProperty<FixedDatePayment>(prop, p => p.payments))
1009 {
1010 // If payment_dates aren't set yet, randomly generate between 1 and 10 payments
1011 // otherwise, ensure we don't generate more than there are payment_dates.
1012 int payment_date_count = ((FixedDatePayment)obj)?.payment_dates?.Count ?? 10;
1013 propertyValue = Enumerable.Range(0, _random.Next(1, payment_date_count + 1))
1014 .Select(_ => _random.NextDouble()).ToList();
1015 }
1016 // In order to ensure the above payment dates are sufficient, restrict randomly generated
1017 // loss sets to types with predictable sequences (no parametric or nested layer loss sets)
1018 // TODO: Downside is we have no test coverage for parametrics/NLLS used in this situation
1019 else if (IsProperty<FixedDatePayment>(prop, p => p.loss_sets))
1021 .Cast<IReference<LossSet>>().ToList();
1022 // ValueAllocators currently allow all base perspectives except for LossGross.
1023 else if (IsProperty<ValueAllocator>(prop, va => va.allocation_perspectives))
1024 {
1026 // 1/3 Chance to include each perspective in the list.
1027 if (_random.Next(3) == 0) newValue.Add(ValueAllocator.Perspective.LossNetOfAggregateTerms);
1028 if (_random.Next(3) == 0) newValue.Add(ValueAllocator.Perspective.ReinstatementPremium);
1029 if (_random.Next(3) == 0) newValue.Add(ValueAllocator.Perspective.ReinstatementBrokerage);
1030 propertyValue = newValue.Any() ? newValue.ToArray() : null;
1031 }
1032 // We generally want to randomize ILayerSource references, but we need to ensure
1033 // that we don't randomly generate one of the special-case forbidden sink types.
1034 else if (Validation_Enabled && IsProperty<Nested>(prop, p => p.sink))
1035 {
1036 // We have to make sure that forbidden layer sink types aren't used as the sink.
1038 // Furthermore, if sources have already been assigned to the current resource, we
1039 // cannot pick a sink that will cause one or more of those sources to become invalid.
1040 // TODO: Keep these checks in sync with those performed in GetLayerSourceRestrictions
1041 if (obj is Nested nestedLayer && (nestedLayer.sources?.Any() ?? false))
1042 {
1043 // Sink can't be a BackAllocatedLayer if any sources are unsupported by it
1048 // Sink can't be a FixedDatePayment if any sources are Payment Patterns
1053 }
1054 propertyValue = GenerateNestedLayerSourceReference(currentRecursion, typeRestrictions);
1055 }
1056 // Same restrictions as above, but handling a list of references
1057 else if (Validation_Enabled && prop.PropertyType == typeof(Nested.Sources) && obj is Nested nestedLayer)
1058 {
1059 // Consider generating fewer sources as we recurse deeper.
1060 int sourcesToGenerate = currentRecursion.Depth > Recursions_Before_Calming_Down ? 1 :
1061 _random.Next(1, 1 + Recursions_Before_Calming_Down - currentRecursion.Depth);
1062 RecursionContext listRecursion = new RecursionContext(typeof(IReference<ILayerSource>), currentRecursion);
1063
1064 // Determine if there are any type restrictions for the nested layer sources.
1065 // Generally, all layer types are allowed, but there's a GOTCHA - if the Nested layer sink
1066 // is a layer with source restrictions (like a BackAllocated layer), we need to account
1067 // for them in generating the nested sources, because these Nested.Sources will be converted
1068 // into sources of the sink layer at the AE - leading to issues on the server.
1069 HashSet<Type> typeRestrictions = nestedLayer.sink == null ? null :
1070 GetLayerSourceRestrictions(new RecursionContext(nestedLayer.sink.ResourceType, currentRecursion));
1071
1072 // Generate some source references
1074 .Select(_ => GenerateNestedLayerSourceReference(listRecursion, typeRestrictions));
1075 // Bug ARE-3553: Saved /layers/ endpoint requires the set of layer references to be unique
1076 if (LayerSourcesRequireLayerReference(listRecursion))
1077 sources = sources.GroupBy(r => r.ref_id, (id, grp) => grp.First());
1078 propertyValue = new Nested.Sources(sources);
1079 }
1080 // Properties that accept layer/layer_view references have some special rules
1081 // Works for ValueAllocator.sink/target/group properties
1082 // Note: Nested.sink is handled separately above because it has a top-level type restriction.
1083 // BackAllocatedLayer.sink is handled within because it has a restriction for all
1084 // subsequent nested layers that exist within its tree.
1085 else if (Validation_Enabled && prop.PropertyType == typeof(IReference<ILayerSource>))
1086 {
1087 propertyValue = GenerateNestedLayerSourceReference(currentRecursion);
1088 }
1089 // If this is a BackAllocatedLayer, the source_id must be changed to match
1090 // the id of something in the new sink.
1091 else if (Validation_Enabled && IsProperty<BackAllocatedLayer>(prop, p => p.source_id))
1092 {
1094 }
1095 // Build a random valid LayerView.layer object
1096 else if (Validation_Enabled && IsProperty<ILayerView>(prop, p => p.layer))
1097 {
1098 // Ensure the layer selected is valid with the analysis_profile
1101 // TODO: This should no longer be necessary, since resource generation routines
1102 // have all been refined to generate valid resources on-the-fly based on
1103 // the globally configured 'Target Analysis Profile'
1105 Resolve(((ILayerView)obj).analysis_profile)),
1106 () => "The generated layer or one of its dependencies is not valid " +
1107 "to use with the current analysis profile.", prop, recursionInfo: currentRecursion);
1108 }
1109 // Build a random PortfolioView.portfolio reference
1110 else if (Validation_Enabled && IsProperty<PortfolioView>(prop, p => p.portfolio))
1111 {
1113 // If this PortfolioView has already specified layer_views, we
1114 // can't also specify a portfolio reference, so 50/50 chance of using each
1115 if ((source.layer_views?.Any() ?? false) && _random.Next(2) == 1)
1116 propertyValue = null;
1117 else
1118 {
1119 source.layer_views = null;
1120 // Ensure the portfolio selected is valid with the analysis_profile
1122 {
1125 StaticPortfolio tentative = Resolve(newValue).ShallowCopy();
1126 int originalLayerCount = tentative.layers.Count;
1127 // Modify the portfolio as necessary to ensure none of its layers
1128 // reference invalid resources.
1129 // TODO: This check should no longer be necessary
1130 tentative.layers = new HashSet<IReference<ILayer>>(tentative.layers
1131 .Where(layer => ResourceIsValidForAnalysisProfile(Resolve(layer),
1132 Resolve(source.analysis_profile))));
1133 // If we ended up removing one or more invalid layers, post the new portfolio
1134 if (tentative.layers.Count != originalLayerCount)
1135 {
1136 currentRecursion.LogIndented("Warning: The StaticPortfolio generated randomly " +
1137 "contained some layers that were not compatible with the source analysis " +
1138 "profile. This should no longer be happening. As a workaround, the invalid " +
1139 "layers have been removed.");
1140 if (!tentative.layers.Any())
1141 return null;
1142 newValue = tentative.Post().ToReference();
1144 }
1145 return newValue;
1146 }, reference => reference != null, () =>
1147 "The generated portfolio reference didn't contain " +
1148 "any layers that are valid to use with the current analysis profile " +
1149 "(due to its event catalog references).", prop, recursionInfo: currentRecursion);
1150 }
1151 }
1152 // OR Build a random list of PortfolioView.layer_views
1153 else if (Validation_Enabled && IsProperty<PortfolioView>(prop, p => p.layer_views))
1154 {
1156 // If this PortfolioView has already specified a Portfolio reference, we
1157 // can't also specify layer_views, so 50/50 chance of using each
1158 if (source.portfolio != null && _random.Next(2) == 1)
1159 propertyValue = null;
1160 else
1161 {
1162 source.portfolio = null;
1164 () => GeneratePortfolioViewLayerViews((PortfolioView)obj, currentRecursion),
1165 set => set.Any(), () => "None of the generated layer_views' Layers " +
1166 "were compatible with this PortfolioView's analysis profile.",
1168 }
1169 }
1170#pragma warning disable 618 //Continued support for testing obsolete classes
1171 // Random PortfolioLossSet.perspective must be 2 characters
1172 else if (Validation_Enabled && IsProperty<PortfolioLossSet>(prop, p => p.perspective))
1173 {
1174 propertyValue = Output.RandomString(2, 2, _random);
1175 }
1176 // Bug: ARE-6040 Using "LossNetOfAggregateTerms" on a NestedLayerLossSet will break the AE/SE
1177 else if (Validation_Enabled && IsProperty<NestedLayerLossSet>(prop, p => p.loss_type) &&
1179 {
1180 propertyValue = LossType.LossGross;
1181 }
1182#pragma warning restore 618
1183 // Random ParametricLossSet allows only certain distributions for each property
1184 else if (Validation_Enabled && (
1185 IsProperty<ParametricLossSet>(prop, p => p.frequency) ||
1186 IsProperty<ParametricLossSet>(prop, p => p.seasonality) ||
1187 IsProperty<ParametricLossSet>(prop, p => p.severity) ||
1188 IsProperty<QCLSLossSet>(prop, p => p.distribution)))
1189 {
1190 propertyValue = GenerateParametricLossSetDistributions(prop, currentRecursion);
1191 }
1192 // transform_records cannot include policies from the forward_records and vice-versa
1193 else if (IsProperty<Policy>(prop, p => p.transform_records))
1194 {
1196 (obj as Policy)?.forward_records ?? new HashSet<RecordType>();
1198 // Exclude RecordTypes already used. 50% chance to use all others.
1199 !existingForwardRecords.Contains(recordType) && _random.Next(2) == 0));
1200 }
1201 else if (IsProperty<Policy>(prop, p => p.forward_records))
1202 {
1204 (obj as Policy)?.transform_records ?? new HashSet<RecordType>();
1206 // Exclude RecordTypes already used. 50% chance to use all others.
1207 !existingTransformRecords.Contains(recordType) && _random.Next(2) == 0));
1208 }
1209 // A random selection of RecordTypes
1210 else if (IsProperty<RecordTypeAnyOfFilter>(prop, p => p.values))
1211 {
1212 RecordType[] allRecordTypes = Enum<RecordType>.Values.ToArray();
1213 propertyValue = allRecordTypes.OrderBy(x => _random.NextDouble())
1214 .Take(_random.Next(1, allRecordTypes.Length)).ToList();
1215 }
1216 // Any random LossFilter.attribute must be derived from the event catalog
1217 else if (Validation_Enabled && IsProperty<AttributeFilter>(prop, l => l.attribute))
1218 {
1219 propertyValue = GenerateAttributeFilterAttribute((AttributeFilter)obj);
1220 }
1221 // Any random LossFilter.value must be derived from the event catalog and attribute
1222 else if (Validation_Enabled && (IsProperty<AnyOfFilter>(prop, p => p.values) ||
1223 IsProperty<ComparisonFilter>(prop, p => p.value)))
1224 {
1226 }
1227 // Make sure RangeFilter.begin_value <= RangeFilter.end_value
1228 else if (Validation_Enabled && IsProperty<RangeFilter>(prop, o => o.end_value))
1229 propertyValue = ((RangeFilter)obj).begin_value +
1230 _random.NextDouble() * (Max_Random - ((RangeFilter)obj).begin_value);
1231 else if (Validation_Enabled && IsProperty<RangeFilter>(prop, o => o.begin_value))
1232 propertyValue = Min_Random +
1233 _random.NextDouble() * (((RangeFilter)obj).end_value - Min_Random);
1234 // Make sure lower <= upper
1237 _random.NextDouble() * (Max_Random - ((UniformDistribution)obj).lower);
1239 propertyValue = Min_Random +
1240 _random.NextDouble() * (((UniformDistribution)obj).upper - Min_Random);
1241 // Also keep Discrete distribution max/mean values small, because these are used as
1242 // frequency distributions and large values lead to simulations that take too long.
1244 propertyValue = _random.Next(((UniformIntDistribution)obj).lower + 1, 50);
1246 propertyValue = _random.Next(0, Math.Max(1, ((UniformIntDistribution)obj).upper - 1));
1248 propertyValue = _random.Next(0, 50);
1249 else if (Validation_Enabled && (
1253 {
1254 propertyValue = _random.NextDouble() * 50;
1255 }
1256 // Modulo is relatively rare in distributions and its presence messes up profiles,
1257 // so increase the chance of generating no modulo.
1258 else if (Validation_Enabled && IsProperty<Distribution>(prop, d => d.modulo))
1259 propertyValue = _random.Next(0, 3) < 2 ? (double?)null : _random.NextDouble() * 100;
1260 // Make sure inception date <= expiry date
1261 else if (Validation_Enabled && IsProperty<ILayer_WithTerms>(prop, l => l.inception_date))
1262 {
1263 // If expiry date is set, pick some random time before expiration, else, a random time
1264 propertyValue = ((ILayer_WithTerms)obj).expiry_date?.AddDays(_random.Next(-365, 0)) ??
1266 }
1267 else if (Validation_Enabled && IsProperty<ILayer_WithTerms>(prop, l => l.expiry_date))
1268 {
1269 // If inception date is set, pick some random time after inception, else, a random time
1270 propertyValue = ((ILayer_WithTerms)obj).inception_date?.AddDays(_random.Next(1, 366)) ??
1272 }
1274 {
1275 // TODO: Fix the Fees object model so that it's compile-time typed, not just a list
1276 // of strings, and then we can remove this and it will randomly generate correctly.
1277 propertyValue = _random.Next(2) == 0 ? null : _random.Next(2) == 0 ?
1278 Samples.Fees.ListOfAllFeeTypes : Samples.Fees.ListWithNestedFeeReference;
1279 }
1280 // Ensure no referenced loss sets have an invalid event catalog.
1281 else if (Validation_Enabled && IsProperty<ILayer_WithLossSets>(prop, l => l.loss_sets))
1282 {
1283 // Ensure the loss sets selected are valid with the analysis_profile
1285 {
1288 // Skip loss sets that reference resources not compatible with the analysis profile
1289 // TODO: This check should no longer be necessary
1290 int originalCount = generated.Count;
1291 generated = generated.Where(ls =>
1293 if (generated.Count != originalCount)
1294 currentRecursion.LogIndented("Warning: The loss_sets list generated randomly " +
1295 "contained some that were not compatible with the target analysis profile." +
1296 "This should no longer be happening. As a workaround, the invalid " +
1297 "loss sets have been removed.");
1298 return generated;
1299 },
1300 result => result.Count > 0, () => "All of the referenced loss sets " +
1301 "referenced an event catalog not in the analysis profile.",
1303 }
1304 // TODO: Cannot randomize optimization parameters much right now as they are too constrained
1305 else if (IsProperty<OptimizationView>(prop, o => o.custom_parameters))
1306 {
1307 // But we can only really randomize the "Discretization" without impacting things
1308 Dictionary<string, object> customParameters = _samples.OptimizationView_CustomParameters;
1309 // ARE-6274: Discretization cannot be bigger than the smallest difference between min and max among
1310 // domain layers. This is why we keep it below 0.01 here, and keep the difference between
1311 // min and max above 0.1 in GenerateOptimizationViewDomains
1312 customParameters["Discretization"] = _random.NextDouble() / 100;
1314 }
1315 // Generate a random population_size
1316 else if (IsProperty<OptimizationView>(prop, o => o.population_size))
1317 {
1318 // Population Size is currently [1,10000] but we don't want to kick off a huge run, so limit to 100
1319 // More than 2, because population sizes of 1 have been linked to issues like ARE-4575
1320 propertyValue = _random.Next(2, 101);
1321 }
1322 // Generate a random iterations
1323 else if (IsProperty<OptimizationView>(prop, o => o.iterations))
1324 {
1325 // Iterations is currently [1,10000] but we don't want to kick off a huge run, so limit to 100
1326 propertyValue = _random.Next(2, 101);
1327 }
1328 // Generate random domains layers
1329 else if (IsProperty<OptimizationView>(prop, o => o.domains))
1330 {
1332 () => GenerateOptimizationViewDomains((OptimizationView)obj, currentRecursion),
1333 domains => domains.Any(), () => "None of the domain's Layers generated " +
1334 "were compatible with this OptimizationView's analysis profile.",
1336 }
1337 // ExchangeRateProfile rate_selection_order should always at least end with a 'Latest'
1338 // rule to ensure a rate can always be found for valid currencies.
1339 else if (IsProperty<ExchangeRateProfile>(prop, p => p.rate_selection_order))
1340 {
1341 object value = CreateRandomizedInstance(prop.PropertyType, currentRecursion);
1343 propertyValue = value;
1344 }
1345 // ExchangeRateProfile monetary_unit_overrides can only contain "Date" and/or "Rate"
1346 else if (IsProperty<ExchangeRateSelectionRule>(prop, o => o.monetary_unit_overrides))
1347 {
1348 List<string> monetary_unit_overrides = new List<string>();
1349 if (_random.Next(2) == 1) monetary_unit_overrides.Add("Date");
1350 if (_random.Next(2) == 1) monetary_unit_overrides.Add("Rate");
1351 propertyValue = monetary_unit_overrides;
1352 }
1353 // Take advantage of the description property to store some debugging info
1354 else if (IsProperty<IStoredAPIResource>(prop, p => p.description))
1355 {
1356#if DEBUG
1357 #if MSTEST
1358 // In debug mode, we can get the method name from our stack to tell us what test produced this resource.
1359 List<MethodBase> methodStack = new StackTrace(false).GetFrames()?.Select(stackFrame => stackFrame.GetMethod()).ToList();
1360 MethodBase topOfStack = methodStack?.Where(m => m.GetCustomAttributes(typeof(TestMethodAttribute), false)
1361 .Any()).FirstOrDefault() ?? methodStack?.Last();
1362 string testName = topOfStack == null ? "Unknown" : $"{topOfStack.ReflectedType?.NiceTypeName()}.{topOfStack.Name}";
1363 #elif NUNIT
1364 string testName = "Unknown";
1365 #endif
1366 propertyValue = $"Randomly Generated {obj.GetType().NiceTypeName()} for test: \"{testName}\". {Output.RandomString(1, 10, _random)}";
1367#else
1368 propertyValue = $"Randomly Generated {obj.GetType().NiceTypeName()}. {Output.RandomString(1, 10, _random)}";
1369#endif
1370 }
1371 // If the property is nullable and has no NotNull constraint,
1372 // have a 50% chance to set the value to null.
1373 else if ((!prop.PropertyType.IsValueType || Nullable.GetUnderlyingType(prop.PropertyType) != null) &&
1374 !prop.IsAttributeDefinedFast<NotNullAttribute>() && _random.Next(2) == 0)
1375 propertyValue = null;
1376 // If the property specifies numeric bounds, generate a number within its bounds.
1377 else if (prop.IsAttributeDefinedFast<LessThanAttribute>() ||
1378 prop.IsAttributeDefinedFast<GreaterThanAttribute>())
1379 {
1380 Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
1381 LessThanAttribute lt = prop.GetCustomAttributeFast<LessThanAttribute>();
1382 GreaterThanAttribute gt = prop.GetCustomAttributeFast<GreaterThanAttribute>();
1383 double interval = underlyingType.IsIntegerType() ? 1.0 : 0.01;
1384 // The minimum/maximum to generate is the most restrictive of the
1385 // globally configured minimum/maximum random number, and
1386 // the minimum allowed by the `GreaterThanAttribute`/`LessThanAttribute`
1387 double minimum = gt == null ? Min_Random : Math.Max(Min_Random,
1388 gt.CanEqual ? gt.GreaterThanValue : gt.GreaterThanValue + interval);
1389 double maximum = lt == null ? Max_Random : Math.Min(Max_Random,
1390 lt.CanEqual ? lt.LessThanValue : lt.LessThanValue - interval);
1391 if (underlyingType.IsIntegerType())
1392 {
1393 // Get the minimum value allowed by the type (e.g. 0 for unsigned types)
1394 // Constrain the "minimum" value further if necessary.
1395 long minSupported = Convert.ToInt64(underlyingType.GetField("MinValue").GetValue(null));
1396 int minInt = (int)Math.Max((long)minimum, minSupported);
1397 int randomInt = _random.Next(minInt, (int)maximum + 1); // +1 because max is exclusive.
1399 }
1400 else
1401 {
1402 double random = _random.NextDouble() * (maximum - minimum) + minimum;
1406 }
1407 }
1408 // Any other property can use the default random generation behaviour for its type.
1409 else
1410 {
1411 object value = CreateRandomizedInstance(prop.PropertyType, currentRecursion);
1412 propertyValue = value;
1413 }
1414
1415 // Now that the property has been given a value, check whether the generated
1416 // value violates any custom attributes. If so, this resource cannot be POSTed
1417 // so we may as well return a null object entirely (and log the issue).
1418 // We only expect this to happen as we get close to our maximum recursion level,
1419 // which constrains our ability to generate resources for required properties.
1421 {
1423 Attribute.GetCustomAttributes(prop, typeof(ValidationAttribute), true))
1425 if (invalidAttributes.Any())
1426 {
1427 throw new PropertyGenerationException("Failed to correctly generate property " +
1428 $"\'{prop.Name}\' for new {obj.GetType().NiceTypeName()}. Value of " +
1429 $"{Output.AutoFormat(propertyValue)} did not meet criteria of the " +
1430 $"validation attributes: {Output.AutoFormat(invalidAttributes)}.");
1431 }
1432 }
1433
1434 return propertyValue;
1435 }
1436
1450 int max_attempts = Max_ReRandomize_Attempts, RecursionContext parentRecursionInfo = null)
1451 {
1452 object newValue = null;
1453 object currentValue = prop.GetValue(obj, null);
1455 string propertyDescription = $"the {prop.ReflectedType.NiceTypeName()} property " +
1456 $"{prop.PropertyType.NiceTypeName()} \'{prop.DeclaringType.NiceTypeName()}.{prop.Name}\'";
1457
1458 // A couple of quick rules we can apply since we know our goal is to *change* the value.
1459 // If the property is a boolean, no need for fanfare, the decision is obvious
1460 if (prop.PropertyType == typeof(bool))
1461 newValue = !(bool)prop.GetValue(obj);
1462 // Special case properties. Some properties cannot be randomized normally.
1463 else if (Validation_Enabled)
1464 {
1465 // Do a sanity check - if the resource has an analysis profile that conflicts with
1466 // the current reflection instance's configuration, many generated property values
1467 // will end up being invalid.
1468 if (obj is IAPIAnalysis analysis && analysis.analysis_profile.ref_id != Target_AnalysisProfile.id)
1469 {
1470 if (IsProperty<IAPIAnalysis>(prop, r => r.analysis_profile))
1471 newValue = Target_AnalysisProfile.ToReference();
1472 else
1473 throw new PropertyGenerationException("Cannot randomly modify properties in a valid way for " +
1474 $"an analysis whose analysis profile ({analysis.analysis_profile.ref_id}) does not match " +
1475 $"the current configured Reflection.Target_AnalysisProfile ({Target_AnalysisProfile.id}).");
1476 }
1477 // Data file properties usually need special handling.
1478 else if (IsProperty<IAPIResource_WithDataEndpoint>(prop, s => s.data_file))
1479 {
1480 // If a data file was given, post a duplicate to get a new one that's guaranteed valid.
1481 if (currentValue != null)
1483 // Otherwise, grab the datafile from the matching sample.
1484 // Special case - if looking for a YLT/YELT loss set, we must select the sample data
1485 // that matches the configuration (csv/binary and without reinstatements if gross)
1486 // Keep in mind that if the loss_type
1487 else if (obj is YELTLossSet yelt && LossType.LossGross ==
1488 (yelt.loss_type ?? LossSet.LossTypeDefault.GetDefaultLossTypeForLossSet(yelt)))
1489 newValue = _samples.LossSet_YELTLossSet.Posted.data_file;
1490 else if (obj is YLTLossSet ylt && LossType.LossGross ==
1492 newValue = _samples.LossSet_YLTLossSet.Posted.data_file;
1493#pragma warning disable 618
1494 else if (obj is YELTLossSet yeltMaybeBinary && yeltMaybeBinary.data_type == YELTLossSet.DataType.binary)
1495 throw new PropertyGenerationException("Cannot assign a random new data_file property " +
1496 "to a binary YELT because there is no sample resource with this type of data.");
1497#pragma warning restore 618
1498 // Other files we can use reflection to grab the data of any sample of the same type
1499 else
1500 {
1501 newValue = (Resolve(GetSampleOfType(obj.GetType()))
1502 as IAPIResource_WithDataEndpoint)?.data_file ??
1503 throw new PropertyGenerationException("No sample resources found of type " +
1504 $"{obj.GetType()} with a {prop.DeclaringType}.data_file to reuse.");
1505 }
1506 }
1507 // Special Case: Analysis profiles are sensitive. In some situations, we cannot
1508 // modify them without invalidating one or more other properties of the same resource.
1509 else if (IsProperty<ILayerView>(prop, r => r.analysis_profile) &&
1511 throw new PropertyGenerationException("Cannot change just the analysis_profile " +
1512 "of this layer_view because it contains nested references to other layer_views " +
1513 "whose analysis_profiles would have to be changed as well to match.");
1514 // PortfolioViews have several properties that cannot be changed without side effects
1515 else if (IsProperty<PortfolioView>(prop, r => r.layer_views) && ((PortfolioView)obj).portfolio != null)
1516 throw new PropertyGenerationException("Cannot give a PortfolioView layer_views " +
1517 "without setting it's portfolio reference to null first.");
1518 else if (IsProperty<PortfolioView>(prop, r => r.portfolio) && ((PortfolioView)obj).layer_views != null)
1519 throw new PropertyGenerationException("Cannot give a PortfolioView a portfolio " +
1520 "without setting it's layer_views collection to null first.");
1521 else if (IsProperty<PortfolioView>(prop, r => r.analysis_profile) && ((PortfolioView)obj).layer_views != null)
1522 throw new PropertyGenerationException("Cannot change just the analysis_profile of a " +
1523 "PortfolioView without changing all its layer_views to match the new analysis_profile.");
1524 // Otherwise, if the property being changed is analysis_profile, we still need to
1525 // generate a new analysis profile with the same event catalogs and loss filters,
1526 // or it might not be valid against the target resource in many other ways.
1527 else if (IsProperty<IAPIAnalysis>(prop, v => v.analysis_profile))
1528 {
1529 // Just post a new analysis profile with all the same information
1530 currentRecursion.LogIndented($"Special case {propertyDescription} - " +
1531 "creating a duplicate analysis profile via re-post.");
1533 }
1534 // Analysis profile catalog must match it's simulation catalog.
1535 else if (IsProperty<AnalysisProfile>(prop, r => r.event_catalogs))
1536 throw new PropertyGenerationException("Cannot change just the event_catalog of an " +
1537 "AnalysisProfile without changing it's simulation to match.");
1538 else if (IsProperty<StaticSimulation>(prop, r => r.event_catalogs) ||
1539 IsProperty<ILossSet_WithEventCatalog>(prop, r => r.event_catalogs))
1540 throw new PropertyGenerationException($"Cannot change the event_catalog of a {prop.ReflectedType} " +
1541 "when validation is enabled, or it becomes invalid to use with this analysis profile.");
1542 // Bug: ARE-3963 optimizations have no currency flexibility.
1543 else if (IsProperty<OptimizationView>(prop, r => r.target_currency))
1544 throw new PropertyGenerationException("Cannot change just the target_currency of an " +
1545 "OptimizationView without changing all its domain layers to match the currency.");
1547 simulatedLossSet.loss_type == LossType.LossNetOfAggregateTerms && simulatedLossSet.data_file != null)
1548 throw new PropertyGenerationException("Cannot just change the loss_type property of a simulated " +
1549 "\"LossNetOfAggregateTerms\" loss set with an assigned data file, " +
1550 "because its data_file may contain reinstatement information.");
1551#pragma warning disable 618
1552 // Bug: ARE-6040 Cannot use "LossNetOfAggregateTerms" on a NestedLayerLossSet
1553 else if (IsProperty<NestedLayerLossSet>(prop, p => p.loss_type) &&
1554 obj is NestedLayerLossSet nlls && nlls.loss_type == LossType.LossGross)
1555 throw new PropertyGenerationException("Cannot change the loss_type property " +
1556 "because \"LossGross\" is the only acceptable value for NestedLayerLossSets.");
1557 else if (IsProperty<YELTLossSet>(prop, p => p.data_type) && obj is YELTLossSet yelt && yelt.data_file != null)
1558 throw new PropertyGenerationException("Cannot change the data_type property of a YELTLossSet " +
1559 "with an assigned data file, because its data_file likely will not match the new data_type.");
1560#pragma warning restore 618
1561 else if (IsProperty<AttributeFilter>(prop, f => f.attribute))
1562 throw new PropertyGenerationException("Cannot change just the attribute of an " +
1563 "AttributeFilter without risking changing the type of values that are valid.");
1564 // Some resources can barly be modified at all
1565 else if (obj is Function && !IsProperty<Function>(prop, r => r.name))
1566 throw new PropertyGenerationException("Cannot change any Optimization.Function property without also " +
1567 "changing its name - because the server requires each Optimization.Function to have a unique name.");
1568 // Easiest way to make a new valid BackAllocatedLayer.sink without invalidating
1569 // the current source_id is wrap the old sink in a nested layer
1570 else if (IsProperty<BackAllocatedLayer>(prop, p => p.sink))
1572 {
1573 sink = _samples.Layer_QuotaShare.AsReference,
1574 sources = new Nested.Sources(((BackAllocatedLayer)obj).sink)
1575 }.Post());
1576 // Easiest way to change FixedDatePayment.payments without risking having an
1577 // insufficient payment dates is to replace with the same number of payments.
1578 else if (obj is FixedDatePayment fdp && IsProperty<FixedDatePayment>(prop, p => p.payments))
1579 newValue = fdp.payments.Select(_ => _random.NextDouble()).ToList();
1580 }
1581 // If any of the above rules determined the required new property value, return now.
1582 if (newValue != null)
1583 {
1584 currentRecursion.LogIndented($"A \"quick rule\" was used to change {propertyDescription} " +
1585 $"on an instance of type {obj.GetType().NiceTypeName()} from a value of " +
1586 $"{Output.AutoFormat(currentValue)} to a value of {Output.AutoFormat(newValue)}");
1587 prop.SetValue(obj, newValue);
1588 return newValue;
1589 }
1590
1591 string last_error = null;
1592 string GenerateFinalError(string errMessage, object duplicateValue) =>
1593 errMessage != null ? $"RandomizePropertyValue method failed: {errMessage}" :
1594 $"The new value generated is equal to the original value ({duplicateValue})";
1595 try
1596 {
1597 newValue = LimitAttempts(() =>
1598 {
1599 last_error = null;
1600 // Suppress errors generating properties and retry
1601 try { return RandomizePropertyValue(obj, prop, currentRecursion); }
1603 {
1604 last_error = ex.Message;
1605 return ex;
1606 }
1607 },
1608 // Hack: If we accidentally generated the same value this property already had
1609 // (worse case 50% chance on e.g. a Boolean property with only 2 valid values),
1610 // we retry up to 'max_attempts' times. If configured to retry up to 20 times,
1611 // this gives a 1/2^20 (0.0001%) chance of failing completely at random.
1612 // If this happens, be suspicious that the RandomizePropertyValue function has
1613 // fallen into a situation where it is only capable of generating a single value
1614 // for the resource & property in question (which is not very 'random').
1615 tentative => IsPropertyValueChanged(currentValue, tentative),
1618 }
1620 {
1621 // Property Generation should be relatively trivial if Validation is disabled.
1622 if (!Validation_Enabled) throw;
1623 // If the original is a stored resource, simply re-posting it should yield a valid duplicate.
1625 newValue = PostCopy(copiable);
1627 newValue = PostCopy(copyableReference);
1628 // If the original is a list with more than one item, we can simply
1629 // remove an item from the list to change its value to a new list.
1630 else if (currentValue is IList asList && asList.Count > 2)
1631 {
1632 IList newList = (IList)Activator.CreateInstance(asList.GetType());
1633 asList.Cast<object>().Skip(1).ToList().ForEach(o => newList.Add(o));
1634 newValue = newList;
1635 }
1636 // If it is a list of IStoredAPIResource references, we can try copying it
1637 else if (currentValue is IList &&
1639 {
1640 IList newList = (IList)Activator.CreateInstance(currentValue.GetType());
1641 newList.Add(PostCopy(copyableList.First()));
1642 newValue = newList;
1643 }
1644 // Re-raise the exception if we couldn't find a fall-back way of generating a value.
1645 if (newValue == null)
1646 throw;
1647 currentRecursion.LogIndented("Found a fallback rule for modifying the property " +
1648 "since random generation failed.");
1649 }
1650
1651 // If successful, log the changed property and return.
1652 currentRecursion.LogIndented($"Changing {propertyDescription} on " +
1653 $"an instance of type {obj.GetType().NiceTypeName()} from a value of " +
1654 $"{Output.AutoFormat(currentValue)} to a value of {Output.AutoFormat(newValue)}");
1655 prop.SetValue(obj, newValue);
1656 return newValue;
1657 }
1658
1665 private static bool IsPropertyValueChanged(object originalValue, object newValue)
1666 {
1667 // Retry if an invalid property value was generated.
1669 return false;
1670 // Test reference equality and simple equivalence before attempting
1671 // the more expensive sequence equality.
1673 return false;
1674 // If the value is an enumerable (other than a string) - test each element
1675 // Note: We must use "Cast" here to create two comparable sequences, in case these
1676 // are IEnumerables of ValueTypes, because ValueTypes must be explicitly boxed.
1678 return !enum1.Cast<object>().SequenceEqual(enum2.Cast<object>());
1679 // Otherwise, test for equivalence using the default "Equals" implementation.
1680 return true;
1681 }
1682
1685 PostCopy(Resolve(reference)).ToReference(true);
1686
1692 {
1693 if (!Validation_Enabled)
1694 throw new Exception("PostCopy should not be used when reflection class " +
1695 "is configured for offline resource generation.");
1698 copy = pollable.PollUntilReady(Samples.DataPollingOptions);
1700 Console.WriteLine($"Created a copy of {resource.GetType().NiceTypeName()} {resource.id} " +
1701 $"with new id {copy.id}");
1702 return copy;
1703 }
1704
1705 #region Specific Property Generation Helper Routines
1708 private bool LayerSourcesRequireLayerReference(RecursionContext recursionInfo) =>
1709 // Only layer_view.layer definitions may contain inlined layers or layer_view references.
1710 !recursionInfo.MatchesCondition(r => typeof(ILayerView).IsAssignableFrom(r.Type)) ||
1711 // If this source is being generated for a layer refence (even if that layer reference is
1712 // being included in a layer_view higher up) - only other layer references are supported.
1713 recursionInfo.MatchesCondition(r => typeof(IReference<ILayer>).IsAssignableFrom(r.Type));
1714
1717 private static HashSet<Type> GetLayerSourceRestrictions(RecursionContext recursionInfo)
1718 {
1720 // ARE-7212: If we are inside a BackAllocatedLayer structure, we are forbidden from using
1721 // PaymentPatterns anywhere between the "sink" and "source_id". Since we have no way of
1722 // determining whether we are past the "source_id" (if it has even been randomly assigned yet)
1723 // the only safe thing to do is not allow any PaymentPatterns within the BackAllocated structure.
1724 if (recursionInfo.MatchesCondition(r => r.Type == typeof(BackAllocatedLayer)))
1726 .ForEach(t => excludedLayerTypes.Add(t));
1727
1728 // No-Issue: If we are inside a FixedDatePayment structure, we cannot risk embedding any other
1729 // FixedDatePayment or DelayedPayment structures within it (even though these are supported).
1730 // This is because the "payment_dates" are extremely strict, and must include dates beyond the
1731 // latest loss generated. There's no way to ensure that inner PaymentPatterns can generate
1732 // valid attributes for themselves while simultaneously guaranteeing that they won't produce occurrences
1733 // later than the latest "payment_dates" of the outer FixedDatePayment. So don't even try.
1734 if (recursionInfo.MatchesCondition(r => r.Type == typeof(FixedDatePayment)))
1736 return excludedLayerTypes;
1737 }
1738
1744 private IReference<ILayerSource> GenerateNestedLayerSourceReference(
1745 RecursionContext recursionInfo, IEnumerable<Type> excludedLayerTypes = null)
1746 {
1747 // The three types of ILayerSource references that might be generated
1748 Type inlinedLayer = typeof(ILayer);
1751
1752 HashSet<Type> typeExclusions = GetLayerSourceRestrictions(recursionInfo);
1753 excludedLayerTypes?.ToList().ForEach(t => typeExclusions.Add(t));
1754
1755 // If there are any restrictions, immediately pick a valid instantiable type
1756 if (typeExclusions.Any())
1757 {
1758 // Choose one of the remaining valid types to generate below
1760 .Except(typeExclusions).OrderBy(t => _random.NextDouble()).First();
1761 layerReference = GetAllInstantiableSubtypes(layerReference).Where(t => /* t is some IReference<TLayer> */
1762 !typeExclusions.Contains(t.GetGenericArguments()[0])).OrderBy(t => _random.NextDouble()).First();
1763 layerViewReference = GetAllInstantiableSubtypes(layerViewReference).Where(t => /* t is some IReference<ILayerView<TLayer>> */
1764 !typeExclusions.Contains(t.GetGenericArguments()[0].GetGenericArguments()[0]))
1765 .OrderBy(t => _random.NextDouble()).First();
1766 // Note: It is still possible that one of the above layers could house a NestedLayerLossSet
1767 // whose layer reference (not generated by this routine) could be a PaymentPattern. But we have stopped
1768 // randomly generating NestedLayerLossSets, so this shouldn't have to be monitored anymore.
1769 }
1770
1771 int roll = _random.Next(5);
1772 if (roll == 0 || LayerSourcesRequireLayerReference(recursionInfo))
1774 // Chance to generate a layer_view reference
1775 if (roll == 1)
1777 // Chance to generate an inlined layer reference (random number generator is set to make
1778 // this the most likely case - because it can be done client-side, so it's quicker)
1780 .Change(l => l.id, null).ToReference();
1781 }
1782
1783 private HashSet<IReference<ILayerView>> GeneratePortfolioViewLayerViews(
1784 PortfolioView owner, RecursionContext recursionInfo)
1785 {
1787 // First, randomize the layer_views collection, but then make changes to meet
1788 // constraints on PortfolioViews.
1791 if (!Validation_Enabled) return random;
1792 foreach (IReference<ILayerView> lv in random)
1793 {
1794 ILayerView temp = Resolve(lv);
1795 bool modified = false;
1796
1797 // All layerViews must have the same analysis profile as this portfolio view.
1798 if (temp.analysis_profile.ref_id != owner.analysis_profile.ref_id)
1799 {
1800 temp.analysis_profile = owner.analysis_profile;
1801 modified = true;
1802 }
1803 // If the layerView references an invalid resource, we have to drop it
1804 // TODO: This check should no longer be necessary
1805 if (Validation_Enabled & !ResourceIsValidForAnalysisProfile(temp.layer, Resolve(temp.analysis_profile)))
1806 {
1807 recursionInfo.LogIndented("Warning: The generated PortfolioView.layer_views list " +
1808 "contained a randomly generated layer that was not compabile with the " +
1809 "target analysis profile. This should no longer be happening. " +
1810 "As a workaround, the invalid layer has been removed: " + temp.layer.Serialize());
1811 continue;
1812 }
1813
1815 // If the analysis profile of the generated layer_view was changed, post the new one.
1816 if (modified)
1817 {
1818 toAdd = temp.Post().ToReference(true);
1820 }
1821 else
1822 toAdd = temp.ToReference();
1823
1824 newlist.Add(toAdd);
1825 }
1826 return newlist;
1827 }
1828
1829 private List<DomainEntry> GenerateOptimizationViewDomains(
1830 OptimizationView owner, RecursionContext recursionInfo)
1831 {
1832 List<DomainEntry> result = new List<DomainEntry>();
1833 // Curate the list of random layer types to choose from before generating domains.
1838 layerRefType.GetGenericArguments()[0])).ToArray();
1839
1840 // Generate between 1 and 5 layers to use as domain entries for the list:
1841 IEnumerable<IReference<ILayer>> layers = Enumerable.Range(1, 1 + _random.Next(5))
1843 supportedTypes[_random.Next(supportedTypes.Length)], recursionInfo));
1844 string currency = owner.target_currency;
1846 {
1847 // Discard any layers found to reference resources not compatible with the analysis profile
1848 // TODO: This check should no longer be necessary
1849 layers = layers.Where(layer => ResourceIsValidForAnalysisProfile(
1850 Resolve(layer), Resolve(owner.analysis_profile)));
1851 // If the target currency isn't set, we may need to derive it to create valid domains
1853 }
1854
1855 // Add all acceptable layers to the domains for this OptimizationView
1856 foreach (IReference<ILayer> layer_ref in layers)
1857 {
1860 {
1861 // Bug: Due to limitation ARE-3963 optimizations don't support layers with different currencies.
1862 ILayer currentLayer = Resolve(layer_ref);
1863 bool layerModified = false;
1864 foreach (PropertyInfo monetaryProperty in currentLayer.GetType().GetPublicProperties_Fast()
1865 .Where(p => p.PropertyType == typeof(MonetaryUnit)))
1866 {
1868 // Handle Bug ARE-5058 while we're here - OE can't handle null monetary units (like premium)
1869 if (monetaryUnit == null)
1871 else if (monetaryUnit.currency != currency)
1872 monetaryUnit.currency = currency;
1873 else
1874 continue;
1875 layerModified = true;
1876 }
1877
1878 // Bug ARE-7228: Optimization Engine crashes when layer has a null description!
1879 if (currentLayer.description == null)
1880 {
1881 currentLayer.description = "Required for Optimization";
1882 layerModified = true;
1883 }
1884
1885 // If we had to change any of the currencies of the terms of the layer, post the changes
1886 if (layerModified)
1887 {
1888 currentLayer = currentLayer.Post();
1889 domainReference = currentLayer.ToReference(true);
1891 }
1892 }
1893
1894 // Create a new DomainEntry from this layer
1896 {
1897 layer = domainReference,
1898 // min can be anything, but this is good enough
1899 min = _random.Next(-2000, 2000) / 1000d,
1900 };
1901 // Max must be >= min. Have a 10% chance to set it equal min (locked)
1902 newDomainEntry.max = _random.Next(10) == 0 ? newDomainEntry.min :
1903 // Otherwise, pick a random number greater than min.
1904 // ARE-6274: Ensure Max is at least 0.1 greater than min, so that we have room
1905 // To randomize the discretization value.
1906 _random.Next((int)(newDomainEntry.min * 1000), 2000) / 1000d + 0.1;
1907 result.Add(newDomainEntry);
1908 }
1909 return result;
1910 }
1911
1912 private string GenerateAttributeFilterAttribute(AttributeFilter owner)
1913 {
1914 const int forbiddenColumns = 2;
1915 string csv = Samples.CSV.Event_Catalog_Data.Replace("\"", "").Replace("\r", "");
1916 int firstLineBreak = csv.IndexOf('\n');
1917 string[] headers = csv.Substring(0, firstLineBreak).Split(',').ToList()
1918 .Skip(forbiddenColumns).ToArray();
1919 // If this is a range filter, we need to restrict headers to numeric fields
1921 {
1922 string[] values = csv.Substring(firstLineBreak + 1,
1923 csv.IndexOf('\n', firstLineBreak + 1) - firstLineBreak - 1).Split(',');
1924 headers = headers.Select((s, i) => new { s, i })
1925 .Where(t => Double.TryParse(values[t.i + forbiddenColumns], out double _))
1926 .Select(t => t.s).ToArray();
1927 if (headers.Length == 0)
1928 Assert.Fail("Reflection-based LossFilter generation error: Could not find " +
1929 "a numeric column for making a filter of type " + owner.GetType().NiceTypeName());
1930 }
1931 return headers[_random.Next(headers.Length)];
1932 }
1933
1935 {
1936 // Parse the event catalog that will be associated with this loss filter
1937 string csv = Samples.CSV.Event_Catalog_Data.Replace("\"", "").Replace("\r", "");
1938 string[] headers = csv.Substring(0, csv.IndexOf('\n')).Split(',');
1939 // Grab a random column from the actual event catalog to filter on
1940 int? index = headers.Select((s, i) => new { s, i }).Where(t => t.s.Equals(
1942 .GetValue(owner, null), StringComparison.OrdinalIgnoreCase))
1943 .Select(t => (int?)t.i).FirstOrDefault();
1944 if (!index.HasValue)
1945 Assert.Fail("Reflection-based LossFilter generation error: Could not find " +
1946 "the randomly assigned LossFilter attribute name.");
1947 // Helper method to find the strongest type to cast the attribute value to.
1948 object GetStrongestType(string asString) =>
1949 Int32.TryParse(asString, out int intValue) ? intValue :
1950 Double.TryParse(asString, out double dblValue) ? dblValue : (object)asString;
1951
1952 // Get all values in the correct column
1953 List<string> values = csv.Split('\n').Select(r => r.Split(',')[index.Value])
1954 // Skip the first value, since it's the header. Then make a distinct list.
1955 .Skip(1).Distinct().ToList();
1956 // Get the filter value from an actual row in the column. Get random row value at index
1957 object value = GetStrongestType(values.ElementAt(_random.Next(values.Count)));
1958
1959 // If this is an AnyOfFilter, pick some random values to create a new list.
1960 if (IsProperty<AnyOfFilter>(prop, f => f.values))
1961 {
1962 List<object> valuesList = values.Where(_ => _random.Next(2) == 0)
1963 .Select(GetStrongestType).ToList();
1964 // Also add the value we picked above (ensures there's at least one item in the list)
1965 if (!valuesList.Contains(value))
1966 valuesList.Add(value);
1967 return valuesList;
1968 }
1969 // Otherwise, the value is just an object we can set directly
1970 object propertyValue = Convert.ChangeType(value, prop.PropertyType);
1971 return propertyValue;
1972 }
1973
1974 private object GenerateParametricLossSetDistributions(PropertyInfo prop, RecursionContext recursionInfo)
1975 {
1977 if (IsProperty<ParametricLossSet>(prop, p => p.frequency))
1978 {
1979 // Sadly, wonky frequency distributions can lead to really long simulation times,
1980 // so only try to generate a CustomFrequencyDistribution
1982 }
1983 else
1984 {
1985 // Seasonality and severity properties allow for any continuous distribution
1987 // But exclude CustomDistribution sub-types that don't correspond with the current property
1989 if (IsProperty<ParametricLossSet>(prop, p => p.seasonality))
1991 else if (IsProperty<ParametricLossSet>(prop, p => p.severity) ||
1992 IsProperty<QCLSLossSet>(prop, p => p.distribution))
1994 }
1995 // Generate a random allowed distribution to sub in here.
1996 Type toGenerate = allowed[_random.Next(allowed.Count)];
1998 }
1999
2003 public string GetRandomCurrency()
2004 {
2005 if (!Validation_Enabled)
2006 return Output.RandomString(3, 3, _random);
2008 return currencies.ElementAt(_random.Next(currencies.Count));
2009 }
2010
2017 {
2018 ILayerSource sink = Resolve(sinkReference);
2019 // If the source definition has loss set or layer_view references, use one
2021 // Take advantage of this recursive test method to collect referenced loss_set and layer_view ids.
2022 // Note: ValueAllocator 'target' and 'group' properties do not count as "sources"
2023 // for back-allocation, so we cannot traverse them to select source ids.
2025 // Gather all nested layer_view ids as potential sources
2027 // Gather all nested loss set ids as potential sources
2028 l is ILayer_WithLossSets withLossSets && !withLossSets.loss_sets.All(ls => sourceIds.Add(ls.ref_id)));
2029
2030 // If the sink was a layer_view reference, its id can also be used as a possible source_id
2031 if (sink is ILayerView sinkAsLayerView)
2032 sourceIds.Add(sinkAsLayerView.id ?? CacheResource(sinkAsLayerView.Post()).ref_id);
2033 // Otherwise, if we haven't found any valid sources ids, raise an error.
2034 // Note: We have no way to know what the parent layer_view (and therefore its
2035 // target_currency) is or will be, so we cannot simply post the sink layer as a
2036 // layer_view to generate a source_id - it might not match the internally generated id.
2037 if (!sourceIds.Any())
2038 throw new PropertyGenerationException("Cannot generate a valid BackAllocatedLayer." +
2039 "source_id because the sink contains no layer_view or loss set ids to reference:\n" +
2040 sink.Serialize());
2041 return sourceIds.Skip(_random.Next(sourceIds.Count)).First();
2042 }
2043 #endregion Specific Property Generation Helper Routines
2044 #endregion Type Specific Random Generation Helpers
2045
2046 #region Validation Helper Methods
2067
2085 int max_attempts = Max_ReRandomize_Attempts, bool exception_on_failure = true,
2087 {
2088 string msg = " to generate an acceptable " + (property == null ? "object" :
2089 $"value for the {property.ReflectedType.NiceTypeName()} property " +
2090 $"{property.DeclaringType.NiceTypeName()}.{property.Name}") +
2091 $" (type {(property?.PropertyType ?? typeof(T)).NiceTypeName()}) failed: ";
2092 T tentative = default(T);
2093 int attempts = 0;
2094 bool valid = false;
2095 string error = msg;
2097 while (attempts++ < max_attempts && !valid)
2098 {
2099 tentative = generator();
2101 if (!valid)
2102 {
2103 error = msg + (get_error?.Invoke() ?? "(no reason given)");
2104 if (attempts < max_attempts)
2105 recursionInfo.LogIndented($"Attempt {attempts}/{max_attempts} {error}");
2106 }
2107 }
2108 if (valid)
2109 return tentative;
2110 error = (max_attempts > 1 ? $"Final attempt ({max_attempts})" : "Attempt") + error;
2113 recursionInfo.LogIndented($"{error} Returning default(T).");
2114 return default(T);
2115 }
2116
2122 // The property expression must resolve to some property info
2124 // Either the runtime PropertyInfo is the same property that the expression indicates,
2125 // or the object that was used to obtain this property derives from TPropertyOwner
2126 // and the current property name matches the name of the desired property
2127 // (presumably because the object inherits, overrides, or hides the base property)
2128 (pi == property || pi.Name == property.Name &&
2129 typeof(TPropertyOwner).IsAssignableFrom(property.ReflectedType));
2130
2131 #region Validation Against Analysis Profile
2132 private readonly ConcurrentDictionary<string, HashSet<string>> _cachedValidCurrenciesByAnalysisProfile =
2134
2136 analysis_profile?.exchange_rate_profile?.ref_id == null ?
2137 throw new ArgumentException("This analysis profile to has no exchange rate profile!") :
2138 _cachedValidCurrenciesByAnalysisProfile.GetOrAdd(analysis_profile.exchange_rate_profile.ref_id, _ =>
2139 {
2140 ExchangeRateTable fxTable = analysis_profile.exchange_rate_profile.GetValue()
2141 .exchange_rate_table.GetValue();
2142 string[] rows = fxTable.data.Get().Split('\n');
2143 int ccyCol = rows[0].Split(',').Where((col, index) => col.ToLower().Contains("currency"))
2144 .Select((col, index) => index).First();
2145 return new HashSet<string>(rows.Skip(1).Select(r => r.Split(',')[ccyCol]),
2146 StringComparer.InvariantCultureIgnoreCase) { fxTable.base_currency };
2147 });
2148
2162 {
2163 if (to_validate == null) return true;
2165 {
2166 // If the root-level resource has been posted and the analysis profile matches, we can
2167 // rest assured that the server has done all analysis_profile-related validation.
2168 // Therefore, any posted resource (i.e. with an id) requires no further validation.
2169 if (apiAnalysis is IAPIResource asResource && asResource.id != null &&
2170 apiAnalysis.analysis_profile.ref_id == rootAp.id)
2171 return true;
2172
2173 // Otherwise, check for conditions that would make this IAPIAnalysis invalid:
2174 // 1. Check that the target currency is valid for the current analysis profile
2175 if (!GetValidCurrenciesForAnalysisProfile(rootAp).Contains(apiAnalysis.target_currency))
2176 return false;
2177 // 2. Ensure all nested analysis objects' analysis profiles match
2178 if (AnyAnalysisMatchesCriteria(apiAnalysis, a => rootAp.id != a.analysis_profile.ref_id))
2179 return false;
2180 }
2181 // Posted resource with a status must have a success status to be used in any context.
2183 asWithStatus.id != null && asWithStatus.status != TaskStatus.Success)
2184 return false;
2185
2186 // Other resources must be checked in depth to see if they *would* pass server validation.
2191
2192 if (to_validate is Portfolio portfolio)
2193 return portfolio.layers.All(lyr =>
2196 return portfolioView.layer_views?.All(lv =>
2200 return optimizationView.domains.All(d =>
2201 ResourceIsValidForAnalysisProfile(Resolve(d.layer), rootAp));
2202 // Any other resources types probably have no validation rules.
2203 return true;
2204 }
2205
2212 {
2213 if (to_validate == null) return true;
2214 // Collect some information used to test all layer definitions
2216 // Build a test function for a single layerSource (layer or layer_view)
2217 // TODO: Double negatives are confusing (returns true when invalid) - try to flip this around
2219 {
2220 // Ensure no referenced layer_views have a conflicting analysis profile
2222 layerView.analysis_profile.ref_id != analysis_profile.id)
2223 return true;
2224 // Test that any monetary units are valid for the analysis profile
2225 // A monetary unit (and thus, this layer) is invalid to use if:
2226 // 1. It references a currency not in the exchange rate table of the analysis profile
2227 // 2. It references a specific "value_date" (difficult to verify - assume not valid)
2228 foreach (PropertyInfo prop in layerSource.GetType().GetUserFacingProperties(true, true, true)
2229 .Where(p => typeof(MonetaryUnit).IsAssignableFrom(p.PropertyType)))
2230 {
2231 if (prop.GetValue(layerSource) is MonetaryUnit mu &&
2232 (!validCurrencies.Contains(mu.currency) || mu.value_date != null))
2233 return true;
2234 }
2235 // Ensure all referenced loss sets are compatible with this profile
2237 layerWithLossSets.loss_sets.Any(ls =>
2238 !ResourceIsValidForAnalysisProfile(Resolve(ls), analysis_profile)))
2239 return true;
2240
2241 // If none of the above checks detected any issues, assume the layer is valid.
2242 return false;
2243 }
2244 // Use a helper to recursively test this and any inlined sources
2246 }
2247
2254 {
2255 if (to_validate == null) return true;
2256 // Collect some information used to test all layer definitions
2259 analysis_profile.event_catalogs.Select(e => e.ref_id));
2260 // Build a test function for a single loss set
2261 // TODO: Double negatives are confusing (returns true when invalid) - try to flip this around
2263 {
2264 // If this loss set has associated data, ensure it has completed processing
2266 lossSetWithData.status != TaskStatus.Success)
2267 return true;
2268 // If this loss set has a currency, ensure it is valid for this analysis profile
2270 !validCurrencies.Contains(asWithCurrency.currency))
2271 return true;
2272 // If this loss set has event catalogs, and any aren't valid, loss set is invalid
2274 withEventCatalog.event_catalogs.Any(e => !validCatalogIds.Contains(e.ref_id)))
2275 return true;
2276 // If this loss set has a simulation (QCLS), and any of the simulation's catalogs
2277 // aren't valid, then this loss set is invalid
2278 if (lossSet is QCLSLossSet asQCLSLossSet && Resolve(asQCLSLossSet.simulation)
2279 .event_catalogs.Any(e => !validCatalogIds.Contains(e.ref_id)))
2280 return true;
2281 // If this is a parametric loss set, ensure its distributions are valid
2283 Resolve(parametric.frequency) is IDiscreteDistribution distribution &&
2285 return true;
2286#pragma warning disable 618
2287 // If this is a NestedLayerLossSet, ensure that its associated loss source
2288 // is valid for the current analysis profile.
2290 !ResourceIsValidForAnalysisProfile(Resolve(asNestedLayerLossSet.layer), analysis_profile))
2291 return true;
2292#pragma warning restore 618
2293 // Otherwise, this loss set appears to be valid.
2294 return false;
2295 }
2296
2297 // Use a helper to recursively test this and any inlined loss sets
2299 }
2300 #endregion Validation Against Analysis Profile
2301 #endregion Validation Helper Methods
2302
2303 #region Request Logging
2304 private int _nestedBrCalls = 0;
2305
2307 public class TimedRequest : Tuple<DateTime, string, RecursionContext, int, int>
2308 {
2310 public string Description => Item2;
2311 public int ThreadId => Item4;
2312
2315
2318 public int NestingLevel => Item5;
2319
2320 public TimedRequest(DateTime start, string description,
2322 : base(start, description, recursionLevel, threadId, nestingLevel) { }
2323 }
2326 private readonly object _requestTrackingLock = new object();
2327
2329 [Conditional("DEBUG")]
2330 private void BeginRequest(RecursionContext recursionInfo, string uniqueKey)
2331 {
2332 TimedRequest reqInfo;
2333 lock (_requestTrackingLock)
2334 {
2335 reqInfo = _requestInfo[uniqueKey] = new TimedRequest(
2336 DateTime.UtcNow, uniqueKey, recursionInfo, Thread.CurrentThread.ManagedThreadId, _nestedBrCalls);
2337 _nestedBrCalls++;
2338 }
2339 recursionInfo.LogIndented($"N:{reqInfo.NestingLevel} " +
2340 $"Thread {reqInfo.ThreadId} Started Request: {uniqueKey}");
2341 }
2342
2344 [Conditional("DEBUG")]
2345 private void EndRequest(string uniqueKey, string message = "")
2346 {
2347 TimedRequest reqInfo;
2348 string log;
2349 lock (_requestTrackingLock)
2350 {
2351 _nestedBrCalls--;
2352 if (_nestedBrCalls < 0)
2353 {
2354 Debug.WriteLine("EndRequest called more than BeginRequest. Timings may be off.");
2355 _nestedBrCalls = 0;
2356 }
2357
2358 reqInfo = _requestInfo[uniqueKey];
2359 log = $"N:{_nestedBrCalls} Thread {reqInfo.ThreadId} " +
2360 $"Finished in {(DateTime.UtcNow - reqInfo.Start).TotalMilliseconds}ms: " +
2361 reqInfo.Description + (message == null ? "" : $" - {message}");
2362 }
2363 reqInfo.RecursionInfo.LogIndented(log);
2364 }
2365 #endregion Request Logging
2366
2367 #region General Helper Methods
2368 // A cache of the list of unsupported types.
2369 private static readonly HashSet<Type> CachedUnsupportedTypes = new HashSet<Type>();
2370 // Used to determine if the cache is populated without locking
2371 private static volatile bool _isCachedAPIResourceCollectionsPopulated = false;
2372
2375 private static HashSet<Type> GetUnsupportedTypes()
2376 {
2377 if (_isCachedAPIResourceCollectionsPopulated)
2378 return CachedUnsupportedTypes;
2379 // Ensure more than one thread doesn't try to populate the list.
2380 lock (CachedUnsupportedTypes)
2381 {
2382 // If another thread populated it since taking the lock, return
2383 if (_isCachedAPIResourceCollectionsPopulated)
2384 return CachedUnsupportedTypes;
2385
2386 // Certain types can appear as inlined objects but should not be considered an
2387 // instantiable type, since they cannot be posted to their collection on their own.
2388 // (e.g.FilterLayer, FixedRateCurrencyConverter)
2390 .Where(t => t.IsAttributeDefinedFast<ObsoleteAttribute>() ||
2391 t.IsAttributeDefinedFast<NotSaveableAttribute>()))
2392 CachedUnsupportedTypes.Add(unusable);
2393 // Add to this list references to any of the above objects
2394 foreach (Type t in CachedUnsupportedTypes.ToList())
2395 CachedUnsupportedTypes.Add(typeof(IReference<>).MakeGenericTypeFast(t));
2396
2397 _isCachedAPIResourceCollectionsPopulated = true;
2398 return CachedUnsupportedTypes;
2399 }
2400 }
2401
2402 private static readonly ConcurrentDictionary<Type, HashSet<Type>> CachedInstantiableSubtypes =
2404
2409 {
2410 HashSet<Type> instantiableSubtypes = CachedInstantiableSubtypes.GetOrAdd(type, T =>
2411 {
2413 // If the requested type itself is not marked as obsolete or otherwise unsupported,
2414 // Remove any instantiable subtypes that derive from unsupported or disabled types.
2415 HashSet<Type> unsupported = GetUnsupportedTypes();
2416 if (!unsupported.Contains(T) && !T.IsAttributeDefinedFast<ObsoleteAttribute>())
2417 subTypes.RemoveWhere(subtype => unsupported.Contains(subtype) ||
2418 unsupported.Any(t_unsupported => t_unsupported.IsAssignableFrom(subtype)));
2419 return subTypes;
2420 });
2421 Assert.AreNotEqual(0, instantiableSubtypes.Count,
2422 $"Could not find any instantiable any objects of type {type.NiceTypeName()}");
2423 return instantiableSubtypes;
2424 }
2425
2426 #region Recursive Tests
2433 {
2434 // Run the test for the current analysis object.
2435 if (test(analysis)) return true;
2436 // Recurse on PortfolioView layer_views.
2437 if (analysis is PortfolioView pv &&
2438 (pv.layer_views?.Any(lv => AnyAnalysisMatchesCriteria(Resolve(lv), test)) ?? false))
2439 return true;
2440 if (analysis is ILayerView analysisAsLayerView)
2441 {
2442 // Recurse on Nested layer_view references.
2446
2448 {
2449 if (TestLayerSource(asNested.sink)) return true;
2450 if (asNested.sources.Any(TestLayerSource)) return true;
2451 }
2453 {
2454 if (TestLayerSource(asValueAllocator.source)) return true;
2455 if (TestLayerSource(asValueAllocator.target)) return true;
2456 if (TestLayerSource(asValueAllocator.group)) return true;
2457 }
2458 }
2459
2460 return false;
2461 }
2462
2466 layerSource == null ? throw new ArgumentNullException(nameof(layerSource)) :
2469 throw new ArgumentException($"Unrecognized {nameof(layerSource)} type: {layerSource.GetType()}");
2470
2480 bool allocationSourcesOnly = false) =>
2483
2497 {
2498 // Run the test for the current layerSource.
2499 if (test(rootLayerSource)) return true;
2500 // Run the test on any nested layers sets in this structure.
2501
2502 // If this source contains a layer definition with nested references, test them recursively
2505 sourceReference != null &&
2507
2509 {
2510 if (TestLayerSource(asNested.sink)) return true;
2511 if (asNested.sources.Any(TestLayerSource)) return true;
2512 }
2514 {
2515 if (TestLayerSource(asValueAllocator.source)) return true;
2517 {
2518 if (TestLayerSource(asValueAllocator.target)) return true;
2519 if (TestLayerSource(asValueAllocator.group)) return true;
2520 }
2521 }
2523 {
2524 if (TestLayerSource(asBackAllocatedLayer.sink)) return true;
2525 }
2526#pragma warning disable 618
2527 // If this layer has loss sets, one or more of them may contain a nested layer.
2529 {
2530 // Return true if any nested loss set contains a layer that returns true for 'test'
2531 // Currently, this is only possible for the NestedLayerLossSet
2532 return asWithLossSets.loss_sets.Any(ls => AnyLossSetMatchesCriteria(Resolve(ls),
2535 }
2536#pragma warning restore 618
2537 return false;
2538 }
2539
2546 {
2547 // Run the test for the current loss set
2548 if (test(root)) return true;
2549 // Run the test on any nested loss sets in this structure.
2550 // If this is a loaded loss set, it has a nested loss set "source"
2552 {
2553 if (AnyLossSetMatchesCriteria(Resolve(asLoaded.source), test))
2554 return true;
2555 }
2556#pragma warning disable 618
2557 // If this is a NestedLayerLossSet, it's layer structure may have one or more loss sets.
2559 {
2560 // Recursively check the loss sets of this layer and any layers it may contain
2561 // Return true IFF this layer has loss sets and any of them return true for 'test'
2564 .loss_sets.Any(ls => AnyLossSetMatchesCriteria(Resolve(ls), test))))
2565 return true;
2566 }
2567#pragma warning restore 618
2568 return false;
2569 }
2570 #endregion Recursive Tests
2571 #endregion General Helper Methods
2572 }
2573
2576 [Serializable]
2577 public class PropertyGenerationException : Exception
2578 {
2579 public PropertyGenerationException(string message) : base(message) { }
2580
2581 public PropertyGenerationException(string message, Exception inner_exception)
2582 : base(message, inner_exception) { }
2583 }
2584}
Exposes the various sample CSV files as strings.
Definition Samples.CSV.cs:9
static string Event_Catalog_Data
static string YELTLossSet_10Trials_ForBinary
static string CustomFrequencyDistribution_Data
static string Exchange_Rate_Table
static string Static_Simulation_Data
static string CustomSeasonalityDistribution_Data
static string YELTLossSet_10Trials
static string YLTLossSet_10Trials
static string CustomSeverityDistribution_Data
static List< Fee > ListWithNestedFeeReference
Definition Samples.cs:88
Exposes sample resource objects, with built-in methods for injecting dependencies.
Definition Samples.cs:14
static readonly PollingOptions DataPollingOptions
Polling options to use when uploading a data file.
static Parameters UploadParams
The LargeDataUpload parameters to use when uploading data for test fixtures.
static readonly Perspective.Base[] AllPerspectiveValues
A list of all perspective values.
static IEnumerable< Perspective > TestPerspectives
Create a list of distinct perspectives to test.
static bool IsFrequencyDistributionSafeToSimulate(IDiscreteDistribution frequencyDistribution)
We should not attempt to simulate a frequency distribution that might generate a huge number of occur...
static HashSet< Type > UnsupportedBackAllocatedLayerSinkTypes
Types that cannot be the sink of a Back-Allocated layer.
static IEnumerable< Type > PaymentPatternTypes
A list of "PaymentPattern" layer types for convenience (because they comes up in a lot of validation ...
Definition Test_Layer.cs:35
static HashSet< Type > UnsupportedNestedSinkTypes
Types that cannot be the sink of a Nested layer.
static readonly HashSet< Type > UnsupportedOptimizationLayerTypes
The layer types which aren't supported by optimization views.
A series of generated messages and formatted strings for use in unit tests.
Definition Output.cs:13
static string RandomString(int minLength, int maxLength, Random randomGenerator=null)
Definition Output.cs:39
An exception throw when a random generation routine fails to produce a POSTable resource.
PropertyGenerationException(string message, Exception inner_exception)
Used to keep track of the recursive random generation process for objects and their attributes....
void LogIndented(string log)
Outputs the log with all newlines indented by an additional amount for every level of recursion.
RecursionContext(Type objectType, RecursionContext prior)
RecursionContext(Type objectType, int depth, RecursionContext prior)
RecursionContext Prior
The previous recursion context.
bool HasParent(Func< RecursionContext, bool > condition=null)
Test whether this RecursionContext has a parent. If a condition is given, returns true only if the pa...
Type Type
The type of resource being generated.
bool MatchesCondition(Func< RecursionContext, bool > condition)
Test whether this RecursionContext or any prior matches the condition.
int Depth
The number of times we have recursed so far.
Small class to track a few properties of a request.
RecursionContext RecursionInfo
Current recursion level as determined by the caller.
int NestingLevel
Current recursion level as determined by the number of calls to "BeginRequest" without "EndRequest".
TimedRequest(DateTime start, string description, RecursionContext recursionLevel, int threadId, int nestingLevel)
A collection of filthy hacks to populate some fields of APIResources objects of any type.
Definition Reflection.cs:41
bool LossSetIsValidForAnalysisProfile(LossSet to_validate, AnalysisProfile analysis_profile)
Validate that this LossSet is compatible with the current analysis profile, including recursive check...
string GetRandomCurrency()
Random currency must be a 3-character currency. In most cases, it must also be a currency for which w...
static IEnumerable< Type > GetAllInstantiableSubtypes(Type type)
Creates a list of types that can be derived from a given type.
bool AnyLayerSourceMatchesCriteria(ILayerSource rootLayerSource, Func< ILayerSource, bool > test, bool allocationSourcesOnly=false)
Recursively determines if the layer, or any inner layers it references match the specified criteria.
string PickBackAllocatedLayerSourceId(IReference< ILayerSource > sinkReference)
Finds a random source loss set or layer_view id within the specified sinkReference which can be used...
bool Validation_Enabled
When true, randomly generated properties will be validated to verify that they can be POSTed successf...
Definition Reflection.cs:83
static ILayer GetLayerDefinition(ILayerSource layerSource)
Convert the ILayerSource to a layer instance by extracting the ILayerView.layer property if it is a I...
T FinalizePostedResource< T >(T posted)
Helper method for ensuring a newly posted resource is left in a finalized state.
bool ResourceIsValidForAnalysisProfile(IAPIResource to_validate, AnalysisProfile rootAp)
Validate that the specified resource is compatible with the current analysis profile (e....
T UploadResourceData< T >(T resource)
If this resource has data endpoints, try uploading some data!
IReference< IAPIResource > GetRandomExistingResource(Type T, RecursionContext currentRecursion)
Tries to find an existing (POSTed) resource of the specified type without generating a new one.
IReference< IAPIResource > GetSampleOfType(Type T)
Get a standard IInjectableResource<T> value matching the given type.
static bool IsProperty< TPropertyOwner >(PropertyInfo property, Expression< Func< TPropertyOwner, object > > propertyExpression)
Determines whether the object instance that returned the property is of the type TPropertyOwner and...
AnalysisProfile Target_AnalysisProfile
The default AnalysisProfile to use for various validation assurances.
Definition Reflection.cs:72
T CreateRandomizedInstance< T >(RecursionContext parentRecursionInfo=null)
Overload of CreateRandomizedInstance that uses a compile-time type parameter and returns a strong typ...
T Resolve< T >(IReference< T > reference)
Resolves a reference in a way that benefits from caching, so that the same reference value never has ...
bool AnyLayerMatchesCriteria(ILayerSource rootlayerSource, Func< ILayer, bool > test, bool allocationSourcesOnly=false)
Recursively determines if the layer, or any inner layers it references match the specified criteria.
static T LimitAttempts< T >(Func< T > generator, Func< T, bool > validation_test, Func< string > get_error, PropertyInfo property=null, int max_attempts=Max_ReRandomize_Attempts, bool exception_on_failure=true, RecursionContext recursionInfo=null)
Attempt to generate a resource randomly, with some validation to see if the attempt was successful....
IReference< T > CacheResource< T >(T resource)
Cache some known posted resource so that it can be reused.
bool AnyAnalysisMatchesCriteria(IAPIAnalysis analysis, Func< IAPIAnalysis, bool > test)
Recursively determines if the layer, or any inner layers it references match the specified criteria.
Reflection(Samples samplesInstance=null, int? seedForRandom=null)
Instantiate a class that can be used to generate random resources via reflection.
Definition Reflection.cs:96
bool AnyLossSetMatchesCriteria(LossSet root, Func< LossSet, bool > test)
Recursively determines if the loss set, or any inner loss sets it references match the specified crit...
HashSet< string > GetValidCurrenciesForAnalysisProfile(AnalysisProfile analysis_profile)
object GenerateAttributeValue(AttributeFilter owner, PropertyInfo prop)
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.
T TryGenerate< T >(Func< T > generator, Func< T, bool > validation_test, Func< string > get_error, PropertyInfo property, bool exception_on_failure=true, RecursionContext recursionInfo=null)
Attempt to generate a resource randomly, with some validation to see if the attempt was successful....
bool LayerSourceIsValidForAnalysisProfile(ILayerSource to_validate, AnalysisProfile analysis_profile)
Validate that this ILayerSource is compatible with the current analysis profile, including recursive ...
Class used in unit tests to mark tests as skipped by using Assert.Inconclusive() method.
Definition SkipUntil.cs:14
A custom exception class that includes the RestSharp.IRestResponse that generated the exception,...
IReference< AnalysisProfile > analysis_profile
The simulation's analysis profile.
Attribute used to define the default value assigned to the target_currency property by the server whe...
static string GetDefaultTargetCurrency(IAPIResource resource)
Determine what the default target currency will be based on the resource type and properties.
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.
A configuration of resources used to simulate a layer or portfolio.
IReference< Simulation > simulation
The Simulation to run.
List< IReference< LossFilter > > loss_filters
The LossFilters to bucket simulation results in.
List< IReference< EventCatalog > > event_catalogs
The EventCatalogs to use during this simulation.
Specifies that the property with this attribute should be greater than the specified amount.
Specifies that the property with this attribute should be less than the specified amount.
Specifies that the property with this attribute cannot be null when POSTing the resource to the serve...
Indicates that while the current APIResource-derived class can be constructed and potentially inlined...
Any attribute that can validate the value of a property.
Represents a file that has been uploaded to the server, usually in conjunction with some other resour...
Definition File.cs:11
Custom Distribution that describes a parametric loss set's frequency.
Custom Distribution that describes a parametric loss set's seasonality.
Custom Distribution that describes a parametric loss set's severity.
Describes a Continuous Uniform Distribution, also known as a rectangular distribution,...
double lower
The lower bound of the domain of the uniform distribution. Can be any real number.
Describes a Discrete Uniform Distribution, also known as a rectangular distribution,...
Representation of an event catalog. The event catalog may cover multiple region/perils,...
A rule indicating that the latest available exchange rate should be used.
A table containing exchange rate information.
Acts as a replacement for IAPIResourceView.back_allocations. Computes the proportion of some sink str...
A "delayed payment" payment pattern models claims being paid in instalments at specific dates followi...
List< double > payments
A list of fractional amounts, denoting how a loss amount will be split into payments....
A wrapper for a List<T> of IReference<ILayerSource> references that simplifies the construction and u...
Definition Nested.cs:67
Allows one or more source layers or layer_views to be attached as loss sources to some other layer de...
Definition Nested.cs:22
IReference< ILayerSource > sink
The layer that will take on the sources' forwarded losses.
Definition Nested.cs:33
Layer Policy is the rule on RecordType that determines how occurrences belonging to a particular Reco...
Definition Policy.cs:17
Surfaces the value-allocator specialty structure (which is treated as a "layer definition" by the API...
Perspective
The loss perspectives that can be used when computing proportions (proportioned_perspective) and allo...
A predicate that determines whether a loss should be included in the perspective or not.
Definition LossFilter.cs:11
A filter that does a test on some attribute.
string attribute
The attribute that is being filtered on.
A filter where the specified attribute's value is compared to some predetermined value.
A filter where the specified attribute must have a numeric value that is within the specified range o...
Definition RangeFilter.cs:8
double begin_value
Values must be greater than this value to be included.
Attribute used to define the default value for the loss_type property.
Definition LossSet.cs:16
static LossType GetDefaultLossTypeForLossSet(ILossSet lossSet)
Indicates what the default loss type would be for a given loss set.
Definition LossSet.cs:27
Base class for all LossSet sub-types. A LossSet is a resource that generates sample (trial) losses wh...
Definition LossSet.cs:13
Representation of a single loss set with an associated event loss table.
Definition ELTLossSet.cs:10
Representation of a Loaded loss set, whose losses are derived from an existing loss set and applies a...
Base for all conventional loss sets, which generate losses, have a server-generated loss profile,...
Base for all loss sets for which pre-generated loss data must be uploaded.
IReference< DataFile > data_file
A reference to the data attached to this resource. This field will automatically be created if you in...
Representation of a Nested layer loss set, which represents any loss set whose losses are derived fro...
Representation of a Parametric loss set.
The Quantile-Based Correlated Loss Sampling (QCLS) loss set is a simulated parametric loss set which ...
Representation of a loss set with an associated year-event-loss table.
DataType
The format of the data uploaded against this YELT.
Representation of a loss set with an associated simulated yearly losses table.
Definition YLTLossSet.cs:11
Representation of a monetary value with a currency.
A structure indicating the min/max share constraint for a layer.
Definition DomainEntry.cs:8
The base class for custom optimization functions.
Representation of a set of Optimization Parameters.
The loss perspective determines what factors to include when computing a distribution.
Base
The set of available values and RecordTypes that can be used to construct a distribution or metrics r...
static Perspective FromEnums(params Base[] perspectives)
Construct a new Perspective that should include the specified Base perspective value or values.
Represents the Analysis of a Portfolio.
IReference< StaticPortfolio > portfolio
The portfolio from which this portfolioView was constructed. Can be supplied by the user on POST inst...
HashSet< IReference< ILayerView > > layer_views
The LayerViews included in this PortfolioView. Must be supplied when posting a new PortfolioView,...
Representation of a portfolio.
Definition Portfolio.cs:12
Representation of a simulation of a set of trials each representing an event occurrence sequence.
Definition Simulation.cs:14
Representation of a portfolio.
A pre-simulated representation of a Simulation (SimGrid).
Utilities that reflect on a type or property expression.
Utility for resolving types given only the types name (Useful for parsing ambiguous JSON objects).
static List< Type > GetAllInstantiableSubtypes(Type type)
Attempts to create a list of types in the current application domain's assemblies derived from the gi...
static Type GetEnumerableType(Type type)
If the specified type implements IEnumerable<T>, returns the generic type argument....
Interface for resources that reference an analysis profile.
IReference< AnalysisProfile > analysis_profile
The analysis profile used to analyze the resource.
string target_currency
The default currency results are returned in. If not specified, the server will automatically select ...
Describes an APIResource class that adds a "/data" sub-resource, since this functionality is common t...
Describes an APIResource class that has a "status" property and corresponding "status_message" which ...
Interface for Base class used by all stored resources.
Represents any distribution that is discrete. With a discrete probability distribution,...
PortfolioView and LayerView interface.
Interface for Base class used by all resources.
Interface shared by all object types and resources returned by the Analyze Re server.
Definition IAPIType.cs:6
An interface for an object that provides access to some layer instance.
Represents the Analysis of a Layer.
ILayer layer
The layer contained in this LayerView.
Definition ILayerView.cs:12
Abstract representation of a layer.
Definition ILayer.cs:7
Base interface for all reference entities.
Abstract representation of a layer with terms.
Abstract representation of a layer with terms.
DateTime? inception_date
The date and time when the contract takes effect.
An interface for pre-simulated loss sets some data associated with them, some hard-coded seasonality ...
Base for all conventional loss sets, which generate losses, have a server-generated loss profile,...
LossType
Indicates what types of losses are generated by this loss set.
Definition LossType.cs:5
@ currency
Reinstatement values represent a fixed dollar amount.
TaskStatus
The status of a data upload which may be in progress.
Definition TaskStatus.cs:9
RecordType
The type of occurrence.
Definition RecordType.cs:10