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> ISecret() { if (typeof(T) == typeof(string)) { return Arb.Generate().Select(s => (ISecret)new SecretAddress(new Uri("http://test"))); } if (typeof(T) == typeof(GithubAuth)) { return Arb.Generate().Select(s => (ISecret)new SecretAddress(new Uri("http://test"))); } else { throw new Exception($"Unsupported secret type {typeof(T)}"); } } 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 RegionGen { get; } = from name in Arb.Generate() where Region.TryParse(name.Get, out _) select Region.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 { get; } = from region in RegionGen from port in Gen.Choose(0, ushort.MaxValue) from scalesetId in Arb.Generate() from machineId in Arb.Generate() from proxyId in Arb.Generate() from dstPort in Gen.Choose(0, ushort.MaxValue) from dstIp in Arb.Generate() from endTime in Arb.Generate() select new ProxyForward( Region: region, Port: port, ScalesetId: scalesetId, MachineId: machineId, ProxyId: proxyId, DstPort: dstPort, DstIp: dstIp.ToString(), EndTime: endTime); public static Gen Proxy { get; } = from region in RegionGen from proxyId in Arb.Generate() from createdTimestamp in Arb.Generate() from state in Arb.Generate() from auth in Arb.Generate() from ip in Arb.Generate() from error in Arb.Generate() from version in Arb.Generate() from heartbeat in Arb.Generate() from outdated in Arb.Generate() select new Proxy( Region: region, ProxyId: proxyId, CreatedTimestamp: createdTimestamp, State: state, Auth: auth, Ip: ip, Error: error, Version: version, Heartbeat: heartbeat, Outdated: outdated); 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() { var config = Arb.Generate>, Tuple?, IDictionary?, IDictionary?, IDictionary?>>>().Select( arg => new InstanceConfig( InstanceName: arg.Item1.Item1, Admins: arg.Item1.Item2, AllowedAadTenants: arg.Item1.Item3, NetworkConfig: arg.Item1.Item4, ProxyNsgConfig: arg.Item1.Item5, Extensions: arg.Item1.Item6, ProxyVmSku: arg.Item1.Item7.Item, RequireAdminPrivileges: arg.Item2.Item1, ApiAccessRules: arg.Item2.Item2, GroupMembership: arg.Item2.Item3, VmTags: arg.Item2.Item4, VmssTags: arg.Item2.Item5 ) ); return config; } 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, Tuple>>>() from poolName in PoolNameGen from region in RegionGen 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: region, Size: arg.Item2.Item1, SpotInstances: arg.Item2.Item2, EphemeralOsDisks: arg.Item2.Item3, NeedsConfigUpdate: arg.Item2.Item4, Error: arg.Item2.Item5, ClientId: arg.Item2.Item6, 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 NoReproReport() { return Arb.Generate>().Select( arg => new NoReproReport( arg.Item1, arg.Item2, arg.Item3, arg.Item4, arg.Item4, arg.Item5, arg.Item3 ) ); } public static Gen CrashTestResult() { return Arb.Generate>().Select( arg => new CrashTestResult( arg.Item1, arg.Item2 ) ); } public static Gen RegressionReport() { return Arb.Generate>().Select( arg => new RegressionReport( arg.Item1, arg.Item2 ) ); } public static Gen ContainerGen { get; } = from len in Gen.Choose(3, 63) from name in Gen.ArrayOf(len, Gen.Elements("abcdefghijklmnopqrstuvwxyz0123456789-")) let nameString = new string(name) where Container.TryParse(nameString, out var _) select Container.Parse(nameString); public static Gen NotificationTemplate() { return Gen.OneOf(new[] { Arb.Generate().Select(e => e as NotificationTemplate), Arb.Generate().Select(e => e as NotificationTemplate), Arb.Generate().Select(e => e as NotificationTemplate) }); } 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> ReadOnlyList() => Arb.Default.List().Convert(x => (IReadOnlyList)x, x => (List)x); 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.ContainerGen); } public static Arbitrary Region() { return Arb.From(OrmGenerators.RegionGen); } public static Arbitrary NotificationTemplate() { return Arb.From(OrmGenerators.NotificationTemplate()); } 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 Arbitrary> ISecret() { return Arb.From(OrmGenerators.ISecret()); } } 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) { return b is null; } if (b is null) { return false; } if (a.Count() != b.Count()) { return false; } foreach (var (first, second) in a.Zip(b)) { if (!AreEqual(first, 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(610100457,297085446); 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 RegressionReport(RegressionReport 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 }); } */ } }