using System; using System.Collections.Generic; using System.Linq; using System.Security; using System.Text.Json; using FsCheck; using FsCheck.Xunit; using Microsoft.OneFuzz.Service; using Microsoft.OneFuzz.Service.OneFuzzLib.Orm; using Xunit.Abstractions; namespace Tests { public class OrmGenerators { public static Gen BaseEvent() { return Gen.OneOf(new[] { Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), }); } public static Gen Uri() { return Arb.Generate().Select( arg => new Uri($"https://{arg.Item.ToString()}:8080") ); } public static Gen Version() { //OneFuzz version uses 3 number version return Arb.Generate>().Select( arg => new Version(arg.Item1, arg.Item2, arg.Item3) ); } public static Gen WebhookMessageLog() { return Arb.Generate, Tuple>>().Select( arg => new WebhookMessageLog( EventId: arg.Item1.Item1, EventType: arg.Item1.Item2.GetEventType(), Event: arg.Item1.Item2, InstanceId: arg.Item1.Item3, InstanceName: arg.Item1.Item4, WebhookId: arg.Item1.Item5, State: arg.Item2.Item1, TryCount: arg.Item2.Item2 )); } public static Gen NodeTasks() { return Arb.Generate>().Select( arg => new NodeTasks( MachineId: arg.Item1, TaskId: arg.Item2, State: arg.Item3 ) ); } public static Gen PoolNameGen { get; } = from name in Arb.Generate() where PoolName.TryParse(name.Get, out _) select PoolName.Parse(name.Get); public static Gen Node { get; } = from arg in Arb.Generate, Tuple>>() from poolName in PoolNameGen select new Node( InitializedAt: arg.Item1.Item1, PoolName: poolName, PoolId: arg.Item1.Item3, MachineId: arg.Item1.Item3, State: arg.Item1.Item4, ScalesetId: arg.Item2.Item1, Heartbeat: arg.Item2.Item2, Version: arg.Item2.Item3, ReimageRequested: arg.Item2.Item4, DeleteRequested: arg.Item2.Item5, DebugKeepNode: arg.Item2.Item6); public static Gen ProxyForward() { return Arb.Generate, Tuple>>().Select( arg => new ProxyForward( Region: arg.Item1.Item1, Port: arg.Item1.Item2.ToString(), ScalesetId: arg.Item1.Item3, MachineId: arg.Item1.Item4, ProxyId: arg.Item1.Item5, DstPort: arg.Item1.Item6, DstIp: arg.Item2.Item1.ToString(), EndTime: arg.Item2.Item2 ) ); } public static Gen Proxy() { return Arb.Generate, Tuple>>().Select( arg => new Proxy( Region: arg.Item1.Item1, ProxyId: arg.Item1.Item2, CreatedTimestamp: arg.Item1.Item3, State: arg.Item1.Item4, Auth: arg.Item1.Item5, Ip: arg.Item1.Item6, Error: arg.Item1.Item7, Version: arg.Item2.Item1, Heartbeat: arg.Item2.Item2, Outdated: arg.Item2.Item3 ) ); } public static Gen EventMessage() { return Arb.Generate>().Select( arg => new EventMessage( EventId: arg.Item1, EventType: arg.Item2.GetEventType(), Event: arg.Item2, InstanceId: arg.Item3, InstanceName: arg.Item4 ) ); } public static Gen NetworkConfig() { return Arb.Generate>().Select( arg => new NetworkConfig( AddressSpace: arg.Item1.Item.ToString(), Subnet: arg.Item2.Item.ToString() ) ); } public static Gen NetworkSecurityGroupConfig() { return Arb.Generate>().Select( arg => new NetworkSecurityGroupConfig( AllowedServiceTags: arg.Item1, AllowedIps: (from ip in arg.Item2 select ip.Item.ToString()).ToArray() ) ); } public static Gen InstanceConfig() { return Arb.Generate, Tuple?, IDictionary?, IDictionary?, IDictionary?>>>().Select( arg => new InstanceConfig( InstanceName: arg.Item1.Item1, Admins: arg.Item1.Item2, AllowPoolManagement: arg.Item1.Item3, AllowedAadTenants: arg.Item1.Item4, NetworkConfig: arg.Item1.Item5, ProxyNsgConfig: arg.Item1.Item6, Extensions: arg.Item1.Item7, ProxyVmSku: arg.Item2.Item1, ApiAccessRules: arg.Item2.Item2, GroupMembership: arg.Item2.Item3, VmTags: arg.Item2.Item4, VmssTags: arg.Item2.Item5 ) ); } public static Gen Task() { return Arb.Generate, Tuple>>().Select( arg => new Task( JobId: arg.Item1.Item1, TaskId: arg.Item1.Item2, State: arg.Item1.Item3, Os: arg.Item1.Item4, Config: arg.Item1.Item5, Error: arg.Item1.Item6, Auth: arg.Item1.Item7, Heartbeat: arg.Item2.Item1, EndTime: arg.Item2.Item2, UserInfo: arg.Item2.Item3 ) ); } public static Gen Scaleset { get; } = from arg in Arb.Generate, Tuple, Guid?>, Tuple>>>() from poolName in PoolNameGen select new Scaleset( PoolName: poolName, ScalesetId: arg.Item1.Item1, State: arg.Item1.Item2, Auth: arg.Item1.Item3, VmSku: arg.Item1.Item4, Image: arg.Item1.Item5, Region: arg.Item1.Item6, Size: arg.Item2.Item1, SpotInstance: arg.Item2.Item2, EphemeralOsDisks: arg.Item2.Item3, NeedsConfigUpdate: arg.Item2.Item4, Error: arg.Item2.Item5, Nodes: arg.Item2.Item6, ClientId: arg.Item2.Item7, ClientObjectId: arg.Item3.Item1, Tags: arg.Item3.Item2); public static Gen Webhook() { return Arb.Generate, string, WebhookMessageFormat>>().Select( arg => new Webhook( WebhookId: arg.Item1, Name: arg.Item2, Url: arg.Item3, EventTypes: arg.Item4, SecretToken: arg.Item5, MessageFormat: arg.Item6 ) ); } public static Gen WebhookMessage() { return Arb.Generate>().Select( arg => new WebhookMessage( EventId: arg.Item1, EventType: arg.Item2.GetEventType(), Event: arg.Item2, InstanceId: arg.Item3, InstanceName: arg.Item4, WebhookId: arg.Item5 ) ); ; } public static Gen WebhookMessageEventGrid() { return Arb.Generate>().Select( arg => new WebhookMessageEventGrid( DataVersion: arg.Item1, Subject: arg.Item2, EventType: arg.Item3.GetEventType(), Data: arg.Item3, Id: arg.Item4, EventTime: arg.Item5 ) ); ; } public static Gen Report() { return Arb.Generate, Guid, int>>().Select( arg => new Report( InputUrl: arg.Item1, InputBlob: arg.Item2, Executable: arg.Item1, CrashType: arg.Item1, CrashSite: arg.Item1, CallStack: arg.Item3, CallStackSha256: arg.Item1, InputSha256: arg.Item1, AsanLog: arg.Item1, TaskId: arg.Item4, JobId: arg.Item4, ScarinessScore: arg.Item5, ScarinessDescription: arg.Item1, MinimizedStack: arg.Item3, MinimizedStackSha256: arg.Item1, MinimizedStackFunctionNames: arg.Item3, MinimizedStackFunctionNamesSha256: arg.Item1, MinimizedStackFunctionLines: arg.Item3, MinimizedStackFunctionLinesSha256: arg.Item1 ) ); } public static Gen Container() { return Arb.Generate>>().Select( arg => new Container(string.Join("", arg.Item1.Get.Where(c => char.IsLetterOrDigit(c) || c == '-'))!) ); } public static Gen Notification() { return Arb.Generate>().Select( arg => new Notification( Container: arg.Item1, NotificationId: arg.Item2, Config: arg.Item3 ) ); } public static Gen Job() { return Arb.Generate?, UserInfo>>().Select( arg => new Job( JobId: arg.Item1, State: arg.Item2, Config: arg.Item3, Error: arg.Item4, EndTime: arg.Item5 ) ); } } public class OrmArb { public static Arbitrary PoolName { get; } = OrmGenerators.PoolNameGen.ToArbitrary(); public static Arbitrary Version() { return Arb.From(OrmGenerators.Version()); } public static Arbitrary Uri() { return Arb.From(OrmGenerators.Uri()); } public static Arbitrary BaseEvent() { return Arb.From(OrmGenerators.BaseEvent()); } public static Arbitrary NodeTasks() { return Arb.From(OrmGenerators.NodeTasks()); } public static Arbitrary Node() { return Arb.From(OrmGenerators.Node); } public static Arbitrary ProxyForward() { return Arb.From(OrmGenerators.ProxyForward()); } public static Arbitrary Proxy() { return Arb.From(OrmGenerators.Proxy()); } public static Arbitrary EventMessage() { return Arb.From(OrmGenerators.EventMessage()); } public static Arbitrary NetworkConfig() { return Arb.From(OrmGenerators.NetworkConfig()); } public static Arbitrary NetworkSecurityConfig() { return Arb.From(OrmGenerators.NetworkSecurityGroupConfig()); } public static Arbitrary InstanceConfig() { return Arb.From(OrmGenerators.InstanceConfig()); } public static Arbitrary WebhookMessageLog() { return Arb.From(OrmGenerators.WebhookMessageLog()); } public static Arbitrary Task() { return Arb.From(OrmGenerators.Task()); } public static Arbitrary Scaleset() => Arb.From(OrmGenerators.Scaleset); public static Arbitrary Webhook() { return Arb.From(OrmGenerators.Webhook()); } public static Arbitrary WebhookMessage() { return Arb.From(OrmGenerators.WebhookMessage()); } public static Arbitrary Report() { return Arb.From(OrmGenerators.Report()); } public static Arbitrary Container() { return Arb.From(OrmGenerators.Container()); } public static Arbitrary Notification() { return Arb.From(OrmGenerators.Notification()); } public static Arbitrary WebhookMessageEventGrid() { return Arb.From(OrmGenerators.WebhookMessageEventGrid()); } public static Arbitrary Job() { return Arb.From(OrmGenerators.Job()); } } public static class EqualityComparison { private static HashSet _baseTypes = new HashSet( new[]{ typeof(byte), typeof(char), typeof(bool), typeof(int), typeof(long), typeof(float), typeof(double), typeof(string), typeof(Guid), typeof(Uri), typeof(DateTime), typeof(DateTime?), typeof(DateTimeOffset), typeof(DateTimeOffset?), typeof(SecureString) }); static bool IEnumerableEqual(IEnumerable? a, IEnumerable? b) { if (a is null && b is null) { return true; } if (a!.Count() != b!.Count()) { return false; } if (a!.Count() == 0 && b!.Count() == 0) { return true; } foreach (var v in a!.Zip(b!)) { if (!AreEqual(v.First, v.Second)) { return false; } } return true; } static bool IDictionaryEqual(IDictionary? a, IDictionary? b, Func cmp) { if (a is null && b is null) return true; if (a!.Count == 0 && b!.Count == 0) return true; if (a!.Count != b!.Count) return false; return a!.Any(v => cmp(v.Value, b[v.Key])); } static bool IDictionaryEqual(IDictionary? a, IDictionary? b) { if (a is null && b is null) return true; if (a!.Count == 0 && b!.Count == 0) return true; if (a!.Count != b!.Count) return false; return a!.Any(v => AreEqual(v.Value, b[v.Key])); } public static bool AreEqual(T r1, T r2) { var t = typeof(T); if (r1 is null && r2 is null) return true; if (_baseTypes.Contains(t)) return r1!.Equals(r2); foreach (var p in t.GetProperties()) { var v1 = p.GetValue(r1); var v2 = p.GetValue(r2); var tt = p.PropertyType; if (v1 is null && v2 is null) continue; if (v1 is null || v2 is null) return false; if (_baseTypes.Contains(tt) && !v1!.Equals(v2)) return false; if (tt.GetInterface("IEnumerable") is not null) { if (!IEnumerableEqual(v1 as IEnumerable, v2 as IEnumerable)) return false; } if (tt.GetInterface("IDictionary") is not null) { if (!IDictionaryEqual(v1 as IDictionary, v2 as IDictionary)) return false; } } return true; } } public class OrmModelsTest { EntityConverter _converter = new EntityConverter(); ITestOutputHelper _output; public OrmModelsTest(ITestOutputHelper output) { Arb.Register(); _output = output; } bool Test(T e) where T : EntityBase { var v = _converter.ToTableEntity(e); var r = _converter.ToRecord(v); return EqualityComparison.AreEqual(e, r); } [Property] public bool Node(Node node) { return Test(node); } [Property] public bool ProxyForward(ProxyForward proxyForward) { return Test(proxyForward); } [Property] public bool Proxy(Proxy proxy) { return Test(proxy); } [Property] public bool Task(Task task) { return Test(task); } [Property] public bool InstanceConfig(InstanceConfig cfg) { return Test(cfg); } [Property] public bool Scaleset(Scaleset ss) { return Test(ss); } [Property] public bool WebhookMessageLog(WebhookMessageLog log) { return Test(log); } [Property] public bool Webhook(Webhook wh) { return Test(wh); } [Property] public bool Notification(Notification n) { return Test(n); } [Property] public bool Job(Job j) { return Test(j); } /* //Sample function on how repro a failing test run, using Replay //functionality of FsCheck. Feel free to [Property] void Replay() { var seed = FsCheck.Random.StdGen.NewStdGen(515508280, 297027790); var p = Prop.ForAll((InstanceConfig x) => InstanceConfig(x) ); p.Check(new Configuration { Replay = seed }); } */ } public class OrmJsonSerialization { JsonSerializerOptions _opts = EntityConverter.GetJsonSerializerOptions(); ITestOutputHelper _output; public OrmJsonSerialization(ITestOutputHelper output) { Arb.Register(); _output = output; } string serialize(T x) { return JsonSerializer.Serialize(x, _opts); } T? deserialize(string json) { return JsonSerializer.Deserialize(json, _opts); } bool Test(T v) { var j = serialize(v); var r = deserialize(j); return EqualityComparison.AreEqual(v, r); } [Property] public bool Node(Node node) { return Test(node); } [Property] public bool ProxyForward(ProxyForward proxyForward) { return Test(proxyForward); } [Property] public bool Proxy(Proxy proxy) { return Test(proxy); } [Property] public bool Task(Task task) { return Test(task); } [Property] public bool InstanceConfig(InstanceConfig cfg) { return Test(cfg); } [Property] public bool Scaleset(Scaleset ss) { return Test(ss); } [Property] public bool WebhookMessageLog(WebhookMessageLog log) { return Test(log); } [Property] public bool Webhook(Webhook wh) { return Test(wh); } [Property] public bool WebhookMessageEventGrid(WebhookMessageEventGrid evt) { return Test(evt); } [Property] public bool WebhookMessage(WebhookMessage msg) { return Test(msg); } [Property] public bool TaskHeartbeatEntry(TaskHeartbeatEntry e) { return Test(e); } [Property] public bool NodeCommand(NodeCommand e) { return Test(e); } [Property] public bool NodeTasks(NodeTasks e) { return Test(e); } [Property] public bool ProxyHeartbeat(ProxyHeartbeat e) { return Test(e); } [Property] public bool ProxyConfig(ProxyConfig e) { return Test(e); } [Property] public bool TaskDetails(TaskDetails e) { return Test(e); } [Property] public bool TaskVm(TaskVm e) { return Test(e); } [Property] public bool TaskPool(TaskPool e) { return Test(e); } [Property] public bool TaskContainers(TaskContainers e) { return Test(e); } [Property] public bool TaskConfig(TaskConfig e) { return Test(e); } [Property] public bool TaskEventSummary(TaskEventSummary e) { return Test(e); } [Property] public bool NodeAssignment(NodeAssignment e) { return Test(e); } [Property] public bool KeyvaultExtensionConfig(KeyvaultExtensionConfig e) { return Test(e); } [Property] public bool AzureMonitorExtensionConfig(AzureMonitorExtensionConfig e) { return Test(e); } [Property] public bool AzureVmExtensionConfig(AzureVmExtensionConfig e) { return Test(e); } [Property] public bool NetworkConfig(NetworkConfig e) { return Test(e); } [Property] public bool NetworkSecurityGroupConfig(NetworkSecurityGroupConfig e) { return Test(e); } [Property] public bool Report(Report e) { return Test(e); } [Property] public bool Notification(Notification e) { return Test(e); } [Property] public bool NoReproReport(NoReproReport e) { return Test(e); } [Property] public bool CrashTestResult(CrashTestResult e) { return Test(e); } [Property] public bool NotificationTemplate(NotificationTemplate e) { return Test(e); } [Property] public bool RegressionReportOrReport(RegressionReportOrReport e) { return Test(e); } [Property] public bool Job(Job e) { return Test(e); } [Property] public bool EventNodeHeartbeat(EventNodeHeartbeat e) { return Test(e); } [Property] public bool EventTaskHeartbeat(EventTaskHeartbeat e) { return Test(e); } [Property] public bool EventTaskStopped(EventTaskStopped e) { return Test(e); } [Property] public bool EventInstanceConfigUpdated(EventInstanceConfigUpdated e) { return Test(e); } [Property] public bool EventProxyCreated(EventProxyCreated e) { return Test(e); } [Property] public bool EventProxyDeleted(EventProxyDeleted e) { return Test(e); } [Property] public bool EventProxyFailed(EventProxyFailed e) { return Test(e); } [Property] public bool EventProxyStateUpdated(EventProxyStateUpdated e) { return Test(e); } [Property] public bool EventCrashReported(EventCrashReported e) { return Test(e); } [Property] public bool EventRegressionReported(EventRegressionReported e) { return Test(e); } [Property] public bool EventFileAdded(EventFileAdded e) { return Test(e); } [Property] public bool EventTaskFailed(EventTaskFailed e) { return Test(e); } [Property] public bool EventTaskStateUpdated(EventTaskStateUpdated e) { return Test(e); } [Property] public bool EventScalesetFailed(EventScalesetFailed e) { return Test(e); } [Property] public bool EventScalesetResizeScheduled(EventScalesetResizeScheduled e) { return Test(e); } [Property] public bool EventScalesetStateUpdated(EventScalesetStateUpdated e) { return Test(e); } [Property] public bool EventNodeDeleted(EventNodeDeleted e) { return Test(e); } [Property] public bool EventNodeCreated(EventNodeCreated e) { return Test(e); } [Property] public bool EventMessage(EventMessage e) { return Test(e); } [Property] public bool Error(Error e) { return Test(e); } [Property] public bool Container(Container c) { return Test(c); } /* //Sample function on how repro a failing test run, using Replay //functionality of FsCheck. Feel free to [Property] void Replay() { var seed = FsCheck.Random.StdGen.NewStdGen(4570702, 297027754); var p = Prop.ForAll((WebhookMessageEventGrid x) => WebhookMessageEventGrid(x) ); p.Check(new Configuration { Replay = seed }); } */ } }