diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 507c288bcf..e7384568ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: python-version: 3.7 - uses: actions/download-artifact@v3 with: - name: build-artifacts + name: artifact-onefuzztypes path: artifacts - name: Build shell: bash @@ -157,7 +157,7 @@ jobs: cp dist/onefuzz.exe ${GITHUB_WORKSPACE}/artifacts/windows-cli/ - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-cli path: artifacts - name: lint shell: bash @@ -220,7 +220,7 @@ jobs: - run: src/ci/onefuzztypes.sh - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-onefuzztypes path: artifacts proxy: runs-on: ubuntu-20.04 @@ -239,7 +239,7 @@ jobs: - run: src/ci/proxy.sh - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-proxy path: artifacts service: runs-on: ubuntu-22.04 @@ -314,19 +314,31 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + - uses: actions/cache@v3 + id: cache-build + with: + key: afl|${{runner.os}}-${{runner.arch}}|${{ hashFiles('src/ci/afl.sh') }} + path: artifacts - run: src/ci/afl.sh + if: steps.cache-build.outputs.cache-hit != 'true' - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-afl path: artifacts aflpp: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + - uses: actions/cache@v3 + id: cache-build + with: + key: aflpp|${{runner.os}}-${{runner.arch}}|${{ hashFiles('src/ci/aflpp.sh') }} + path: artifacts - run: src/ci/aflpp.sh + if: steps.cache-build.outputs.cache-hit != 'true' - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-aflpp path: artifacts bicep-check: name: Check Bicep files @@ -352,7 +364,7 @@ jobs: shell: bash - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-dotnet-fuzzing-tools-linux path: artifacts dotnet-fuzzing-tools-windows: runs-on: windows-2022 @@ -367,7 +379,7 @@ jobs: shell: pwsh - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-dotnet-fuzzing-tools-windows path: artifacts radamsa-linux: runs-on: ubuntu-20.04 @@ -378,13 +390,13 @@ jobs: with: # key on the shell script only: this script fixes the # version to a particular commit, so if it changes we need to rebuild - key: radamsa-linux-${{ hashFiles('src/ci/radamsa-linux.sh') }} + key: radamsa|${{runner.os}}-${{runner.arch}}|${{ hashFiles('src/ci/radamsa-linux.sh') }} path: artifacts - run: src/ci/radamsa-linux.sh if: steps.cache-radamsa-build-linux.outputs.cache-hit != 'true' - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-radamsa-linux path: artifacts radamsa-win64: runs-on: windows-2019 @@ -395,24 +407,25 @@ jobs: with: # key on the shell script only: this script fixes the # version to a particular commit, so if it changes we need to rebuild - key: radamsa-windows-${{ hashFiles('src/ci/radamsa-windows.sh') }} + key: radamsa|${{runner.os}}-${{runner.arch}}|${{ hashFiles('src/ci/radamsa-windows.sh') }} path: artifacts - run: c:\msys64\usr\bin\bash src/ci/radamsa-windows.sh if: steps.cache-radamsa-build-windows.outputs.cache-hit != 'true' - uses: actions/upload-artifact@v3 with: - name: build-artifacts + name: artifact-radamsa-windows path: artifacts package: needs: - agent - azcopy - cli - - onefuzztypes - proxy - service - afl - aflpp + - dotnet-fuzzing-tools-linux + - dotnet-fuzzing-tools-windows - radamsa-linux - radamsa-win64 runs-on: ubuntu-20.04 @@ -422,6 +435,38 @@ jobs: with: name: build-artifacts path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-cli + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-proxy + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-radamsa-linux + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-radamsa-windows + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-afl + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-aflpp + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-dotnet-fuzzing-tools-linux + path: artifacts + - uses: actions/download-artifact@v3 + with: + name: artifact-dotnet-fuzzing-tools-windows + path: artifacts - uses: actions/setup-python@v4 with: python-version: 3.7 @@ -436,6 +481,7 @@ jobs: isort --profile black . --check black . --check flake8 *.py + rm -r .mypy_cache - name: Package Onefuzz run: | set -ex @@ -540,7 +586,8 @@ jobs: - uses: actions/upload-artifact@v3 with: - name: integration-test-artifacts + # NB: this name is used by check-pr.py + name: artifact-integration-tests-linux path: src/integration-tests/artifacts build-integration-tests-windows: runs-on: windows-2019 @@ -623,52 +670,39 @@ jobs: shell: powershell - uses: actions/upload-artifact@v3 with: - name: integration-test-artifacts + # NB: this name is used by check-pr.py + name: artifact-integration-tests-windows path: src/integration-tests/artifacts integration-tests-linux: runs-on: ubuntu-20.04 needs: - build-integration-tests-linux - dotnet-fuzzing-tools-linux - # even though this job doesn't use the artifacts for these other jobs, - # we must include them or we get spurious failures when the download-artifact - # step tries to download the named artifact which includes files from - # all of these jobs - - agent - - azcopy - - cli - - onefuzztypes - - proxy - - service - - afl - - aflpp - - radamsa-linux - - radamsa-win64 steps: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - name: build-artifacts - path: build-artifacts + name: artifact-dotnet-fuzzing-tools-linux + path: dotnet-fuzzing-tools-linux - uses: actions/download-artifact@v3 with: - name: integration-test-artifacts - path: integration-test-artifacts + name: artifact-integration-tests-linux + path: integration-tests-linux - name: test shell: bash run: | set -ex -o pipefail # Must be absolute paths. - export GOODBAD_DOTNET="${GITHUB_WORKSPACE}/integration-test-artifacts/GoodBadDotnet" + export GOODBAD_DOTNET="${GITHUB_WORKSPACE}/integration-tests-linux/GoodBadDotnet" - export LIBFUZZER_DOTNET="${GITHUB_WORKSPACE}/build-artifacts/third-party/dotnet-fuzzing-linux/libfuzzer-dotnet/libfuzzer-dotnet" + export LIBFUZZER_DOTNET="${GITHUB_WORKSPACE}/dotnet-fuzzing-tools-linux/third-party/dotnet-fuzzing-linux/libfuzzer-dotnet/libfuzzer-dotnet" chmod +x $LIBFUZZER_DOTNET - export LIBFUZZER_DOTNET_LOADER="${GITHUB_WORKSPACE}/build-artifacts/third-party/dotnet-fuzzing-linux/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader" + export LIBFUZZER_DOTNET_LOADER="${GITHUB_WORKSPACE}/dotnet-fuzzing-tools-linux/third-party/dotnet-fuzzing-linux/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader" chmod +x $LIBFUZZER_DOTNET_LOADER - export SHARPFUZZ="${GITHUB_WORKSPACE}/build-artifacts/third-party/dotnet-fuzzing-linux/sharpfuzz/SharpFuzz.CommandLine" + export SHARPFUZZ="${GITHUB_WORKSPACE}/dotnet-fuzzing-tools-linux/third-party/dotnet-fuzzing-linux/sharpfuzz/SharpFuzz.CommandLine" chmod +x $SHARPFUZZ ./src/ci/test-libfuzzer-dotnet.sh diff --git a/src/ApiService/ApiService/ApiService.csproj b/src/ApiService/ApiService/ApiService.csproj index c41a852def..38c68b3919 100644 --- a/src/ApiService/ApiService/ApiService.csproj +++ b/src/ApiService/ApiService/ApiService.csproj @@ -11,6 +11,7 @@ + diff --git a/src/ApiService/ApiService/Functions/Jobs.cs b/src/ApiService/ApiService/Functions/Jobs.cs index 3064e550dd..261fb237c2 100644 --- a/src/ApiService/ApiService/Functions/Jobs.cs +++ b/src/ApiService/ApiService/Functions/Jobs.cs @@ -46,14 +46,16 @@ public class Jobs { var job = new Job( JobId: Guid.NewGuid(), State: JobState.Init, - Config: cfg) { - UserInfo = userInfo.UserInfo, - }; + Config: cfg, + UserInfo: new( + ObjectId: userInfo.UserInfo.ObjectId, + ApplicationId: userInfo.UserInfo.ApplicationId)); // create the job logs container var metadata = new Dictionary{ { "container_type", "logs" }, // TODO: use ContainerType.Logs enum somehow; needs snake case name }; + var containerName = Container.Parse($"logs-{job.JobId}"); var containerSas = await _context.Containers.CreateContainer(containerName, StorageType.Corpus, metadata); if (containerSas is null) { @@ -79,9 +81,9 @@ public class Jobs { ), "job"); } - await _context.Events.SendEvent(new EventJobCreated(job.JobId, job.Config, job.UserInfo)); - return await RequestHandling.Ok(req, JobResponse.ForJob(job)); + await _context.Events.SendEvent(new EventJobCreated(job.JobId, job.Config, job.UserInfo)); + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo: null)); } private async Task Delete(HttpRequestData req) { @@ -111,7 +113,7 @@ public class Jobs { } } - return await RequestHandling.Ok(req, JobResponse.ForJob(job)); + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo: null)); } private async Task Get(HttpRequestData req) { @@ -135,11 +137,10 @@ public class Jobs { // TODO: search.WithTasks is not checked in Python code? var taskInfo = await _context.TaskOperations.SearchStates(jobId).Select(TaskToJobTaskInfo).ToListAsync(); - job = job with { TaskInfo = taskInfo }; - return await RequestHandling.Ok(req, JobResponse.ForJob(job)); + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo)); } var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty()).ToListAsync(); - return await RequestHandling.Ok(req, jobs.Select(j => JobResponse.ForJob(j))); + return await RequestHandling.Ok(req, jobs.Select(j => JobResponse.ForJob(j, taskInfo: null))); } } diff --git a/src/ApiService/ApiService/Functions/Node.cs b/src/ApiService/ApiService/Functions/Node.cs index 92d9536036..6d8ad921bb 100644 --- a/src/ApiService/ApiService/Functions/Node.cs +++ b/src/ApiService/ApiService/Functions/Node.cs @@ -59,7 +59,7 @@ public class Node { _context.NodeMessageOperations.GetMessages(machineId).ToListAsync().AsTask()); var commands = messages.Select(m => m.Message).ToList(); - return await RequestHandling.Ok(req, NodeToNodeSearchResult(node with { Tasks = tasks, Messages = commands })); + return await RequestHandling.Ok(req, NodeToNodeSearchResult(node, tasks, commands)); } var nodes = await _context.NodeOperations.SearchStates( @@ -67,10 +67,10 @@ public class Node { poolName: search.PoolName, scalesetId: search.ScalesetId).ToListAsync(); - return await RequestHandling.Ok(req, nodes.Select(NodeToNodeSearchResult)); + return await RequestHandling.Ok(req, nodes.Select(x => NodeToNodeSearchResult(x, null, null))); } - private static NodeSearchResult NodeToNodeSearchResult(Service.Node node) { + private static NodeSearchResult NodeToNodeSearchResult(Service.Node node, List? tasks, List? messages) { return new NodeSearchResult( PoolId: node.PoolId, PoolName: node.PoolName, @@ -82,7 +82,9 @@ public class Node { ScalesetId: node.ScalesetId, ReimageRequested: node.ReimageRequested, DeleteRequested: node.DeleteRequested, - DebugKeepNode: node.DebugKeepNode); + DebugKeepNode: node.DebugKeepNode, + Tasks: tasks, + Messages: messages); } private async Async.Task Patch(HttpRequestData req) { diff --git a/src/ApiService/ApiService/Functions/TimerRetention.cs b/src/ApiService/ApiService/Functions/TimerRetention.cs index bcc58d9af2..284dcdbfb3 100644 --- a/src/ApiService/ApiService/Functions/TimerRetention.cs +++ b/src/ApiService/ApiService/Functions/TimerRetention.cs @@ -71,45 +71,6 @@ public class TimerRetention { } } - await foreach (var job in _jobOps.QueryAsync(Query.And(timeFilter, Query.EqualEnum("state", JobState.Enabled)))) { - if (job.UserInfo is not null && job.UserInfo.Upn is not null) { - _log.LogInformation("removing PII from job {JobId}", job.JobId); - var userInfo = job.UserInfo with { Upn = null }; - var updatedJob = job with { UserInfo = userInfo }; - var r = await _jobOps.Replace(updatedJob); - if (!r.IsOk) { - _log.AddHttpStatus(r.ErrorV); - _log.LogError("Failed to save job {JobId}", updatedJob.JobId); - } - } - } - - await foreach (var task in _taskOps.QueryAsync(Query.And(timeFilter, Query.EqualEnum("state", TaskState.Stopped)))) { - if (task.UserInfo is not null && task.UserInfo.Upn is not null) { - _log.LogInformation("removing PII from task {TaskId}", task.TaskId); - var userInfo = task.UserInfo with { Upn = null }; - var updatedTask = task with { UserInfo = userInfo }; - var r = await _taskOps.Replace(updatedTask); - if (!r.IsOk) { - _log.AddHttpStatus(r.ErrorV); - _log.LogError("Failed to save task {TaskId}", updatedTask.TaskId); - } - } - } - - await foreach (var repro in _reproOps.QueryAsync(timeFilter)) { - if (repro.UserInfo is not null && repro.UserInfo.Upn is not null) { - _log.LogInformation("removing PII from repro: {VmId}", repro.VmId); - var userInfo = repro.UserInfo with { Upn = null }; - var updatedRepro = repro with { UserInfo = userInfo }; - var r = await _reproOps.Replace(updatedRepro); - if (!r.IsOk) { - _log.AddHttpStatus(r.ErrorV); - _log.LogError("Failed to save repro {VmId}", updatedRepro.VmId); - } - } - } - //delete Task queues for tasks that do not exist in the table (manually deleted from the table) //delete Pool queues for pools that were deleted before https://github.com/microsoft/onefuzz/issues/2430 got fixed await foreach (var q in _queue.ListQueues(StorageType.Corpus)) { diff --git a/src/ApiService/ApiService/OneFuzzTypes/Events.cs b/src/ApiService/ApiService/OneFuzzTypes/Events.cs index 2726a9b0a0..d81e083db4 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Events.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Events.cs @@ -106,7 +106,7 @@ public class EventTypeProvider : ITypeProvider { public record EventTaskStopped( Guid JobId, Guid TaskId, - UserInfo? UserInfo, + StoredUserInfo? UserInfo, TaskConfig Config ) : BaseEvent(); @@ -115,7 +115,7 @@ public record EventTaskFailed( Guid JobId, Guid TaskId, Error Error, - UserInfo? UserInfo, + StoredUserInfo? UserInfo, TaskConfig Config ) : BaseEvent(); @@ -124,7 +124,7 @@ TaskConfig Config public record EventJobCreated( Guid JobId, JobConfig Config, - UserInfo? UserInfo + StoredUserInfo? UserInfo ) : BaseEvent(); @@ -139,7 +139,7 @@ public record JobTaskStopped( public record EventJobStopped( Guid JobId, JobConfig Config, - UserInfo? UserInfo, + StoredUserInfo? UserInfo, List TaskInfo ) : BaseEvent(), ITruncatable { public BaseEvent Truncate(int maxLength) { @@ -155,7 +155,7 @@ public record EventTaskCreated( Guid JobId, Guid TaskId, TaskConfig Config, - UserInfo? UserInfo + StoredUserInfo? UserInfo ) : BaseEvent(); [EventType(EventType.TaskStateUpdated)] @@ -375,7 +375,7 @@ public DownloadableEventMessage(Guid EventId, EventType EventType, BaseEvent Eve public record EventMessage( Guid EventId, EventType EventType, - [property: TypeDiscrimnatorAttribute("EventType", typeof(EventTypeProvider))] + [property: TypeDiscrimnator("EventType", typeof(EventTypeProvider))] [property: JsonConverter(typeof(BaseEventConverter))] BaseEvent Event, Guid InstanceId, @@ -401,7 +401,7 @@ public record EventMessage( public class BaseEventConverter : JsonConverter { public override BaseEvent? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return null; + throw new NotSupportedException("BaseEvent cannot be read"); } public override void Write(Utf8JsonWriter writer, BaseEvent value, JsonSerializerOptions options) { diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs index 671305c41e..7bbc24127a 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs @@ -39,8 +39,8 @@ public record TaskHeartbeatEntry( Guid TaskId, Guid? JobId, Guid MachineId, - HeartbeatData[] Data - ); + HeartbeatData[] Data); + public record NodeHeartbeatEntry(Guid NodeId, HeartbeatData[] Data); public record NodeCommandStopIfFree(); @@ -51,7 +51,6 @@ public record StopTaskNodeCommand(Guid TaskId); public record NodeCommandAddSshKey(string PublicKey); - public record NodeCommand ( [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -114,11 +113,7 @@ public record Node bool DeleteRequested = false, bool DebugKeepNode = false, bool Managed = true -) : StatefulEntityBase(State) { - - public List? Tasks { get; set; } - public List? Messages { get; set; } -} +) : StatefulEntityBase(State) { } public record Forward @@ -287,7 +282,7 @@ public record Task( ISecret? Auth = null, DateTimeOffset? Heartbeat = null, DateTimeOffset? EndTime = null, - UserInfo? UserInfo = null) : StatefulEntityBase(State) { + StoredUserInfo? UserInfo = null) : StatefulEntityBase(State) { } public record TaskEvent( @@ -727,7 +722,7 @@ public record Repro( Error? Error = null, string? Ip = null, DateTimeOffset? EndTime = null, - UserInfo? UserInfo = null + StoredUserInfo? UserInfo = null ) : StatefulEntityBase(State); // TODO: Make this >1 and < 7*24 (more than one hour, less than seven days) @@ -908,12 +903,13 @@ public record Job( [PartitionKey][RowKey] Guid JobId, JobState State, JobConfig Config, + StoredUserInfo? UserInfo, string? Error = null, DateTimeOffset? EndTime = null -) : StatefulEntityBase(State) { - public List? TaskInfo { get; set; } - public UserInfo? UserInfo { get; set; } -} +) : StatefulEntityBase(State) { } + +// This is like UserInfo but lacks the UPN: +public record StoredUserInfo(Guid? ApplicationId, Guid? ObjectId); public record Nsg(string Name, Region Region) { public static Nsg ForRegion(Region region) diff --git a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs index ee78f7b3cd..d9c3dd48af 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs @@ -33,8 +33,9 @@ public record NodeSearchResult( ScalesetId? ScalesetId, bool ReimageRequested, bool DeleteRequested, - bool DebugKeepNode -) : BaseResponse(); + bool DebugKeepNode, + List? Tasks, + List? Messages) : BaseResponse(); public record TaskSearchResult( Guid JobId, @@ -46,7 +47,7 @@ public record TaskSearchResult( Authentication? Auth, DateTimeOffset? Heartbeat, DateTimeOffset? EndTime, - UserInfo? UserInfo, + StoredUserInfo? UserInfo, List Events, List Nodes, [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat @@ -96,18 +97,20 @@ public record JobResponse( string? Error, DateTimeOffset? EndTime, List? TaskInfo, + StoredUserInfo? UserInfo, [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat DateTimeOffset? Timestamp // not including UserInfo from Job model ) : BaseResponse() { - public static JobResponse ForJob(Job j) + public static JobResponse ForJob(Job j, List? taskInfo) => new( JobId: j.JobId, State: j.State, Config: j.Config, Error: j.Error, EndTime: j.EndTime, - TaskInfo: j.TaskInfo, + TaskInfo: taskInfo, + UserInfo: j.UserInfo, Timestamp: j.Timestamp ); } @@ -237,7 +240,7 @@ public record ReproVmResponse( Error? Error = null, string? Ip = null, DateTimeOffset? EndTime = null, - UserInfo? UserInfo = null + StoredUserInfo? UserInfo = null ) : BaseResponse() { public static ReproVmResponse FromRepro(Repro repro, Authentication? auth) { diff --git a/src/ApiService/ApiService/OneFuzzTypes/Webhooks.cs b/src/ApiService/ApiService/OneFuzzTypes/Webhooks.cs index 5f2a5660ef..550ae4a129 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Webhooks.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Webhooks.cs @@ -39,7 +39,7 @@ WebhookMessage Data public record WebhookMessageLog( [RowKey] Guid EventId, EventType EventType, - [property: TypeDiscrimnatorAttribute("EventType", typeof(EventTypeProvider))] + [property: TypeDiscrimnator("EventType", typeof(EventTypeProvider))] [property: JsonConverter(typeof(BaseEventConverter))] BaseEvent Event, Guid InstanceId, diff --git a/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs b/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs index ec5c66cd93..cd3e289402 100644 --- a/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/ReproOperations.cs @@ -351,7 +351,7 @@ await _context.ReproOperations.GetSetupContainer(repro) Os: task.Os, Auth: new SecretAddress(auth), EndTime: DateTimeOffset.UtcNow + TimeSpan.FromHours(config.Duration), - UserInfo: userInfo); + UserInfo: new(ObjectId: userInfo.ObjectId, ApplicationId: userInfo.ApplicationId)); var r = await _context.ReproOperations.Insert(vm); if (!r.IsOk) { diff --git a/src/ApiService/ApiService/onefuzzlib/TaskOperations.cs b/src/ApiService/ApiService/onefuzzlib/TaskOperations.cs index 18e4af9c86..a7a4cd0ebb 100644 --- a/src/ApiService/ApiService/onefuzzlib/TaskOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/TaskOperations.cs @@ -215,14 +215,19 @@ public TaskOperations(ILogger log, IMemoryCache cache, IOnefuzzC return OneFuzzResult.Error(ErrorCode.INVALID_CONFIGURATION, "task must have vm or pool"); } - var task = new Task(jobId, Guid.NewGuid(), TaskState.Init, os, config, UserInfo: userInfo); + var storedUserInfo = new StoredUserInfo( + ApplicationId: userInfo.ApplicationId, + ObjectId: userInfo.ObjectId); + + var task = new Task(jobId, Guid.NewGuid(), TaskState.Init, os, config, UserInfo: storedUserInfo); var r = await _context.TaskOperations.Insert(task); if (!r.IsOk) { _logTracer.AddHttpStatus(r.ErrorV); _logTracer.LogError("failed to insert task {TaskId}", task.TaskId); } - await _context.Events.SendEvent(new EventTaskCreated(jobId, task.TaskId, config, userInfo)); + + await _context.Events.SendEvent(new EventTaskCreated(jobId, task.TaskId, config, storedUserInfo)); _logTracer.LogInformation("created task {JobId} {TaskId} {TaskType}", jobId, task.TaskId, task.Config.Task.Type); return OneFuzzResult.Ok(task); diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/JinjaTemplateAdapter.cs b/src/ApiService/ApiService/onefuzzlib/notifications/JinjaTemplateAdapter.cs index fa5c07c480..38acbc5970 100644 --- a/src/ApiService/ApiService/onefuzzlib/notifications/JinjaTemplateAdapter.cs +++ b/src/ApiService/ApiService/onefuzzlib/notifications/JinjaTemplateAdapter.cs @@ -196,7 +196,7 @@ public class JinjaTemplateAdapter { new SecretValue(new Authentication("password", "public key", "private key")), DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, - new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn") + new(Guid.NewGuid(), Guid.NewGuid()) ); var job = new Job( @@ -209,6 +209,7 @@ public class JinjaTemplateAdapter { duration, "logs" ), + null, "some error", DateTimeOffset.UtcNow ); diff --git a/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs b/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs index ddc01f34c2..f1ae42dfa3 100644 --- a/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs +++ b/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; using Azure; using Azure.Data.Tables; @@ -12,8 +13,7 @@ public abstract record EntityBase { [JsonIgnore] public ETag? ETag { get; set; } - [JsonPropertyName("Timestamp")] - // this needs to be serialized with a capital T for backwards compat + [JsonIgnore] public DateTimeOffset? Timestamp { get; set; } // https://docs.microsoft.com/en-us/rest/api/storageservices/designing-a-scalable-partitioning-strategy-for-azure-table-storage#yyy @@ -49,6 +49,7 @@ public class SkipRenameAttribute : Attribute { } public class RowKeyAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter)] public class PartitionKeyAttribute : Attribute { } + [AttributeUsage(AttributeTargets.Property)] public class TypeDiscrimnatorAttribute : Attribute { public string FieldName { get; } @@ -166,8 +167,7 @@ public class EntityConverter { return _cache.GetOrAdd(typeof(T), type => { var constructor = type.GetConstructors()[0]; var parameterInfos = constructor.GetParameters(); - var parameters = - parameterInfos.SelectMany(GetEntityProperties).ToArray(); + var parameters = parameterInfos.SelectMany(GetEntityProperties).ToArray(); return new EntityInfo(typeof(T), parameters.ToLookup(x => x.name), BuildConstructerFrom(constructor)); }); @@ -177,6 +177,48 @@ public class EntityConverter { public static T? FromJsonString(string value) => JsonSerializer.Deserialize(value, _options); + private async ValueTask<(string, object?)> PropertyToColumnValue(EntityProperty prop, object? value) { + if (value == null) { + return (prop.columnName, null); + } + + if (prop.kind == EntityPropertyKind.PartitionKey || prop.kind == EntityPropertyKind.RowKey) { + return (prop.columnName, value?.ToString()); + } + + if (prop.type == typeof(Guid) || prop.type == typeof(Guid?) || prop.type == typeof(Uri)) { + return (prop.columnName, value?.ToString()); + } + + if (prop.type == typeof(bool) + || prop.type == typeof(bool?) + || prop.type == typeof(string) + || prop.type == typeof(DateTime) + || prop.type == typeof(DateTime?) + || prop.type == typeof(DateTimeOffset) + || prop.type == typeof(DateTimeOffset?) + || prop.type == typeof(int) + || prop.type == typeof(int?) + || prop.type == typeof(long) + || prop.type == typeof(long?) + || prop.type == typeof(double) + || prop.type == typeof(double?)) { + return (prop.columnName, value); + } + + // if prop.type is a SecretData + if (typeof(ISecret).IsAssignableFrom(prop.type)) { + var secret = (ISecret)value; + if (!secret.IsHIddden) { + var kv = await _secretsOperations.StoreSecret(secret); + value = new SecretAddress(kv); + } + } + + var serialized = JsonSerializer.Serialize(value, _options); + return (prop.columnName, serialized.Trim('"')); + } + public async Async.Task ToTableEntity(T typedEntity) where T : EntityBase { if (typedEntity == null) { throw new ArgumentNullException(nameof(typedEntity)); @@ -185,51 +227,15 @@ public class EntityConverter { var type = typeof(T); var entityInfo = GetEntityInfo(); - Dictionary columnValues = await entityInfo.properties.SelectMany(x => x).ToAsyncEnumerable().SelectAwait(async prop => { - var value = entityInfo.type.GetProperty(prop.name)?.GetValue(typedEntity); - if (value == null) { - return (prop.columnName, value: (object?)null); - } - if (prop.kind == EntityPropertyKind.PartitionKey || prop.kind == EntityPropertyKind.RowKey) { - return (prop.columnName, value?.ToString()); - } - if (prop.type == typeof(Guid) || prop.type == typeof(Guid?) || prop.type == typeof(Uri)) { - return (prop.columnName, value?.ToString()); - } - if (prop.type == typeof(bool) - || prop.type == typeof(bool?) - || prop.type == typeof(string) - || prop.type == typeof(DateTime) - || prop.type == typeof(DateTime?) - || prop.type == typeof(DateTimeOffset) - || prop.type == typeof(DateTimeOffset?) - || prop.type == typeof(int) - || prop.type == typeof(int?) - || prop.type == typeof(long) - || prop.type == typeof(long?) - || prop.type == typeof(double) - || prop.type == typeof(double?) - - ) { - return (prop.columnName, value); - } - // if prop.type is a SecretData - if (typeof(ISecret).IsAssignableFrom(prop.type)) { - var secret = (ISecret)value; - if (!secret.IsHIddden) { - var kv = await _secretsOperations.StoreSecret(secret); - value = new SecretAddress(kv); - } - } - - var serialized = JsonSerializer.Serialize(value, _options); - return (prop.columnName, serialized.Trim('"')); - - }).ToDictionaryAsync(x => x.columnName, x => x.value); + var columnValues = new Dictionary(); + foreach (var prop in entityInfo.properties.SelectMany(x => x)) { + var value = entityInfo.type.GetProperty(prop.name)?.GetValue(typedEntity); + var (columnName, columnValue) = await PropertyToColumnValue(prop, value); + columnValues.Add(columnName, columnValue); + } var tableEntity = new TableEntity(columnValues); - if (typedEntity.ETag.HasValue) { tableEntity.ETag = typedEntity.ETag.Value; } diff --git a/src/ApiService/ApiService/packages.lock.json b/src/ApiService/ApiService/packages.lock.json index 6a842a063b..9a75abe763 100644 --- a/src/ApiService/ApiService/packages.lock.json +++ b/src/ApiService/ApiService/packages.lock.json @@ -332,6 +332,15 @@ "System.Text.Encodings.Web": "7.0.0" } }, + "Microsoft.Rest.ClientRuntime": { + "type": "Direct", + "requested": "[2.3.24, )", + "resolved": "2.3.24", + "contentHash": "hZH7XgM3eV2jFrnq7Yf0nBD4WVXQzDrer2gEY7HMNiwio2hwDsTHO6LWuueNQAfRpNp4W7mKxcXpwXUiuVIlYw==", + "dependencies": { + "Newtonsoft.Json": "10.0.3" + } + }, "Microsoft.TeamFoundationServer.Client": { "type": "Direct", "requested": "[19.219.0-preview, )", @@ -1097,14 +1106,6 @@ "resolved": "1.1.3", "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" }, - "Microsoft.Rest.ClientRuntime": { - "type": "Transitive", - "resolved": "2.3.20", - "contentHash": "bw/H1nO4JdnhTagPHWIFQwtlQ6rb2jqw5RTrqPsPqzrjhJxc7P6MyNGdf4pgHQdzdpBSNOfZTEQifoUkxmzYXQ==", - "dependencies": { - "Newtonsoft.Json": "10.0.3" - } - }, "Microsoft.Rest.ClientRuntime.Azure": { "type": "Transitive", "resolved": "3.3.19", diff --git a/src/ApiService/FunctionalTests/1f-api/ApiClient.cs b/src/ApiService/FunctionalTests/1f-api/ApiClient.cs index 2055ee9215..e06974ea88 100644 --- a/src/ApiService/FunctionalTests/1f-api/ApiClient.cs +++ b/src/ApiService/FunctionalTests/1f-api/ApiClient.cs @@ -1,7 +1,5 @@ namespace FunctionalTests { sealed class ApiClient { - static Uri endpoint = new Uri(System.Environment.GetEnvironmentVariable("ONEFUZZ_ENDPOINT") ?? "http://localhost:7071"); - static Microsoft.Morse.AuthenticationConfig authConfig = new Microsoft.Morse.AuthenticationConfig( ClientId: System.Environment.GetEnvironmentVariable("ONEFUZZ_CLIENT_ID")!, @@ -14,6 +12,6 @@ sealed class ApiClient { public static Microsoft.OneFuzz.Service.Request Request => request; - public static Uri Endpoint => endpoint; + public static Uri Endpoint { get; } = new(Environment.GetEnvironmentVariable("ONEFUZZ_ENDPOINT") ?? "http://localhost:7071"); } } diff --git a/src/ApiService/FunctionalTests/FunctionalTests.csproj b/src/ApiService/FunctionalTests/FunctionalTests.csproj index 1c0868136c..9b2a658e9f 100644 --- a/src/ApiService/FunctionalTests/FunctionalTests.csproj +++ b/src/ApiService/FunctionalTests/FunctionalTests.csproj @@ -19,12 +19,12 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/ApiService/FunctionalTests/packages.lock.json b/src/ApiService/FunctionalTests/packages.lock.json index c0c895987e..030754b980 100644 --- a/src/ApiService/FunctionalTests/packages.lock.json +++ b/src/ApiService/FunctionalTests/packages.lock.json @@ -4,9 +4,9 @@ "net7.0": { "coverlet.collector": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "xjY8xBigSeWIYs4I7DgUHqSNoGqnHi7Fv7/7RZD02rvZyG3hlsjnQKiVKVWKgr9kRKgmV+dEfu8KScvysiC0Wg==" + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "tW3lsNS+dAEII6YGUX/VMoJjBS1QvsxqJeqLaJXub08y1FSjasFPtQ4UBUsudE9PNrzLjooClMsPtY2cZLdXpQ==" }, "FluentAssertions": { "type": "Direct", @@ -90,20 +90,20 @@ }, "xunit": { "type": "Direct", - "requested": "[2.4.2, )", - "resolved": "2.4.2", - "contentHash": "6Mj73Ont3zj2CJuoykVJfE0ZmRwn7C+pTuRP8c4bnaaTFjwNG6tGe0prJ1yIbMe9AHrpDys63ctWacSsFJWK/w==", + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "f2V5wuAdoaq0mRTt9UBmPbVex9HcwFYn+y7WaKUz5Xpakcrv7lhtQWBJUWNY4N3Z+o+atDBLyAALM1QWx04C6Q==", "dependencies": { - "xunit.analyzers": "1.0.0", - "xunit.assert": "2.4.2", - "xunit.core": "[2.4.2]" + "xunit.analyzers": "1.2.0", + "xunit.assert": "2.5.0", + "xunit.core": "[2.5.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[2.4.5, )", - "resolved": "2.4.5", - "contentHash": "OwHamvBdUKgqsXfBzWiCW/O98BTx81UKzx2bieIOQI7CZFE5NEQZGi8PBQGIKawDW96xeRffiNf20SjfC0x9hw==" + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "+Gp9vuC2431yPyKB15YrOTxCuEAErBQUTIs6CquumX1F073UaPHGW0VE/XVJLMh9W4sXdz3TBkcHdFWZrRn2Hw==" }, "Microsoft.CodeCoverage": { "type": "Transitive", @@ -1030,30 +1030,30 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "BeO8hEgs/c8Ls2647fPfieMngncvf0D0xYNDfIO59MolxtCtVjFRd6SRc+7tj8VMqkVOuJcnc9eh4ngI2cAmLQ==" + "resolved": "1.2.0", + "contentHash": "d3dehV/DASLRlR8stWQmbPPjfYC2tct50Evav+OlsJMkfFqkhYvzO1k0s81lk0px8O0knZU/FqC8SqbXOtn+hw==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "pxJISOFjn2XTTi1mcDCkRZrTFb9OtRRCtx2kZFNF51GdReLr1ls2rnyxvAS4JO247K3aNtflvh5Q0346K5BROA==", + "resolved": "2.5.0", + "contentHash": "wN84pKX5jzfpgJ0bB6arrCA/oelBeYLCpnQ9Wj5xGEVPydKzVSDY5tEatFLHE/rO0+0RC+I4H5igGE118jRh1w==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "xunit.core": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "KB4yGCxNqIVyekhJLXtKSEq6BaXVp/JO3mbGVE1hxypZTLEe7h+sTbAhpA+yZW2dPtXTuiW+C1B2oxxHEkrmOw==", + "resolved": "2.5.0", + "contentHash": "dnV0Mn2s1C0y2m33AylQyMkEyhBQsL4R0302kwSGiEGuY3JwzEmhTa9pnghyMRPliYSs4fXfkEAP+5bKXryGFg==", "dependencies": { - "xunit.extensibility.core": "[2.4.2]", - "xunit.extensibility.execution": "[2.4.2]" + "xunit.extensibility.core": "[2.5.0]", + "xunit.extensibility.execution": "[2.5.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "W1BoXTIN1C6kpVSMw25huSet25ky6IAQUNovu3zGOGN/jWnbgSoTyCrlIhmXSg0tH5nEf8q7h3OjNHOjyu5PfA==", + "resolved": "2.5.0", + "contentHash": "xRm6NIV3i7I+LkjsAJ91Xz2fxJm/oMEi2CYq1G5HlGTgcK1Zo2wNbLO6nKX1VG5FZzXibSdoLwr/MofVvh3mFA==", "dependencies": { "NETStandard.Library": "1.6.1", "xunit.abstractions": "2.0.3" @@ -1061,11 +1061,11 @@ }, "xunit.extensibility.execution": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "CZmgcKkwpyo8FlupZdWpJCryrAOWLh1FBPG6gmVZuPQkGQsim/oL4PcP4nfrC2hHgXUFtluvaJ0Sp9PQKUMNpg==", + "resolved": "2.5.0", + "contentHash": "7+v2Bvp+1ew1iMGQVb1glICi8jcNdHbRUX6Ru0dmJBViGdjiS7kyqcX2VxleQhFbKNi+WF0an7/TeTXD283RlQ==", "dependencies": { "NETStandard.Library": "1.6.1", - "xunit.extensibility.core": "[2.4.2]" + "xunit.extensibility.core": "[2.5.0]" } } } diff --git a/src/ApiService/IntegrationTests/AdoTests.cs b/src/ApiService/IntegrationTests/AdoTests.cs index 2a2d7b67a8..798ca72ab5 100644 --- a/src/ApiService/IntegrationTests/AdoTests.cs +++ b/src/ApiService/IntegrationTests/AdoTests.cs @@ -61,7 +61,7 @@ public AdoTestBase(ITestOutputHelper output, IStorage storage) string.Empty, new Report(null, null, string.Empty, string.Empty, string.Empty, new(), string.Empty, string.Empty, null, Guid.Empty, Guid.Empty, null, null, null, null, null, null, null, null, null, null, null, null), new Task(Guid.Empty, Guid.Empty, TaskState.Init, Os.Windows, new TaskConfig(Guid.Empty, null, new TaskDetails(TaskType.LibfuzzerFuzz, 1))), - new Job(Guid.Empty, JobState.Init, new JobConfig(string.Empty, string.Empty, string.Empty, 1, null)), + new Job(Guid.Empty, JobState.Init, new JobConfig(string.Empty, string.Empty, string.Empty, 1, null), null), new Uri("https://example.com"), new Uri("https://example.com"), new Uri("https://example.com"), diff --git a/src/ApiService/IntegrationTests/EventsTests.cs b/src/ApiService/IntegrationTests/EventsTests.cs index d711f95c0c..613848d31c 100644 --- a/src/ApiService/IntegrationTests/EventsTests.cs +++ b/src/ApiService/IntegrationTests/EventsTests.cs @@ -89,11 +89,9 @@ public EventsTestBase(ITestOutputHelper output, IStorage storage) await Context.Events.SendEvent(new EventTaskStopped( jobId, taskId, - new UserInfo( + new StoredUserInfo( appId, - objectId, - upn - ), + objectId), taskConfig )); diff --git a/src/ApiService/IntegrationTests/IntegrationTests.csproj b/src/ApiService/IntegrationTests/IntegrationTests.csproj index e3974d68a1..f8877268dc 100644 --- a/src/ApiService/IntegrationTests/IntegrationTests.csproj +++ b/src/ApiService/IntegrationTests/IntegrationTests.csproj @@ -9,12 +9,12 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/ApiService/IntegrationTests/JobsTests.cs b/src/ApiService/IntegrationTests/JobsTests.cs index f52bccd373..adc7be2e26 100644 --- a/src/ApiService/IntegrationTests/JobsTests.cs +++ b/src/ApiService/IntegrationTests/JobsTests.cs @@ -44,7 +44,7 @@ public JobsTestBase(ITestOutputHelper output, IStorage storage) [Fact] public async Async.Task Delete_ExistingJob_SetsStoppingState() { await Context.InsertAll( - new Job(_jobId, JobState.Enabled, _config)); + new Job(_jobId, JobState.Enabled, _config, null)); var func = new Jobs(Context, LoggerProvider.CreateLogger()); @@ -63,7 +63,7 @@ public JobsTestBase(ITestOutputHelper output, IStorage storage) [Fact] public async Async.Task Delete_ExistingStoppedJob_DoesNotSetStoppingState() { await Context.InsertAll( - new Job(_jobId, JobState.Stopped, _config)); + new Job(_jobId, JobState.Stopped, _config, null)); var func = new Jobs(Context, LoggerProvider.CreateLogger()); @@ -83,7 +83,7 @@ public JobsTestBase(ITestOutputHelper output, IStorage storage) [Fact] public async Async.Task Get_CanFindSpecificJob() { await Context.InsertAll( - new Job(_jobId, JobState.Stopped, _config)); + new Job(_jobId, JobState.Stopped, _config, null)); var func = new Jobs(Context, LoggerProvider.CreateLogger()); @@ -96,13 +96,32 @@ public JobsTestBase(ITestOutputHelper output, IStorage storage) Assert.Equal(JobState.Stopped, response.State); } + + [Fact] + public async Async.Task Get_ReturnsUserData() { + var userInfo = new StoredUserInfo(Guid.NewGuid(), Guid.NewGuid()); + + await Context.InsertAll( + new Job(_jobId, JobState.Stopped, _config, userInfo)); + + var func = new Jobs(Context, LoggerProvider.CreateLogger()); + + var ctx = new TestFunctionContext(); + var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId)), ctx); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + + var response = BodyAs(result); + Assert.Equal(_jobId, response.JobId); + Assert.Equal(userInfo, response.UserInfo); + } + [Fact] public async Async.Task Get_CanFindJobsInState() { await Context.InsertAll( - new Job(Guid.NewGuid(), JobState.Init, _config), - new Job(Guid.NewGuid(), JobState.Stopping, _config), - new Job(Guid.NewGuid(), JobState.Enabled, _config), - new Job(Guid.NewGuid(), JobState.Stopped, _config)); + new Job(Guid.NewGuid(), JobState.Init, _config, null), + new Job(Guid.NewGuid(), JobState.Stopping, _config, null), + new Job(Guid.NewGuid(), JobState.Enabled, _config, null), + new Job(Guid.NewGuid(), JobState.Stopped, _config, null)); var func = new Jobs(Context, LoggerProvider.CreateLogger()); @@ -118,10 +137,10 @@ public JobsTestBase(ITestOutputHelper output, IStorage storage) [Fact] public async Async.Task Get_CanFindMultipleJobsInState() { await Context.InsertAll( - new Job(Guid.NewGuid(), JobState.Init, _config), - new Job(Guid.NewGuid(), JobState.Stopping, _config), - new Job(Guid.NewGuid(), JobState.Enabled, _config), - new Job(Guid.NewGuid(), JobState.Stopped, _config)); + new Job(Guid.NewGuid(), JobState.Init, _config, null), + new Job(Guid.NewGuid(), JobState.Stopping, _config, null), + new Job(Guid.NewGuid(), JobState.Enabled, _config, null), + new Job(Guid.NewGuid(), JobState.Stopped, _config, null)); var func = new Jobs(Context, LoggerProvider.CreateLogger()); diff --git a/src/ApiService/IntegrationTests/packages.lock.json b/src/ApiService/IntegrationTests/packages.lock.json index 1beaf7bba4..5a257bda33 100644 --- a/src/ApiService/IntegrationTests/packages.lock.json +++ b/src/ApiService/IntegrationTests/packages.lock.json @@ -4,9 +4,9 @@ "net7.0": { "coverlet.collector": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "xjY8xBigSeWIYs4I7DgUHqSNoGqnHi7Fv7/7RZD02rvZyG3hlsjnQKiVKVWKgr9kRKgmV+dEfu8KScvysiC0Wg==" + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "tW3lsNS+dAEII6YGUX/VMoJjBS1QvsxqJeqLaJXub08y1FSjasFPtQ4UBUsudE9PNrzLjooClMsPtY2cZLdXpQ==" }, "FluentAssertions": { "type": "Direct", @@ -47,20 +47,20 @@ }, "xunit": { "type": "Direct", - "requested": "[2.4.2, )", - "resolved": "2.4.2", - "contentHash": "6Mj73Ont3zj2CJuoykVJfE0ZmRwn7C+pTuRP8c4bnaaTFjwNG6tGe0prJ1yIbMe9AHrpDys63ctWacSsFJWK/w==", + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "f2V5wuAdoaq0mRTt9UBmPbVex9HcwFYn+y7WaKUz5Xpakcrv7lhtQWBJUWNY4N3Z+o+atDBLyAALM1QWx04C6Q==", "dependencies": { - "xunit.analyzers": "1.0.0", - "xunit.assert": "2.4.2", - "xunit.core": "[2.4.2]" + "xunit.analyzers": "1.2.0", + "xunit.assert": "2.5.0", + "xunit.core": "[2.5.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[2.4.5, )", - "resolved": "2.4.5", - "contentHash": "OwHamvBdUKgqsXfBzWiCW/O98BTx81UKzx2bieIOQI7CZFE5NEQZGi8PBQGIKawDW96xeRffiNf20SjfC0x9hw==" + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "+Gp9vuC2431yPyKB15YrOTxCuEAErBQUTIs6CquumX1F073UaPHGW0VE/XVJLMh9W4sXdz3TBkcHdFWZrRn2Hw==" }, "Azure.Core": { "type": "Transitive", @@ -1075,8 +1075,8 @@ }, "Microsoft.Rest.ClientRuntime": { "type": "Transitive", - "resolved": "2.3.20", - "contentHash": "bw/H1nO4JdnhTagPHWIFQwtlQ6rb2jqw5RTrqPsPqzrjhJxc7P6MyNGdf4pgHQdzdpBSNOfZTEQifoUkxmzYXQ==", + "resolved": "2.3.24", + "contentHash": "hZH7XgM3eV2jFrnq7Yf0nBD4WVXQzDrer2gEY7HMNiwio2hwDsTHO6LWuueNQAfRpNp4W7mKxcXpwXUiuVIlYw==", "dependencies": { "Newtonsoft.Json": "10.0.3" } @@ -2460,30 +2460,30 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "BeO8hEgs/c8Ls2647fPfieMngncvf0D0xYNDfIO59MolxtCtVjFRd6SRc+7tj8VMqkVOuJcnc9eh4ngI2cAmLQ==" + "resolved": "1.2.0", + "contentHash": "d3dehV/DASLRlR8stWQmbPPjfYC2tct50Evav+OlsJMkfFqkhYvzO1k0s81lk0px8O0knZU/FqC8SqbXOtn+hw==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "pxJISOFjn2XTTi1mcDCkRZrTFb9OtRRCtx2kZFNF51GdReLr1ls2rnyxvAS4JO247K3aNtflvh5Q0346K5BROA==", + "resolved": "2.5.0", + "contentHash": "wN84pKX5jzfpgJ0bB6arrCA/oelBeYLCpnQ9Wj5xGEVPydKzVSDY5tEatFLHE/rO0+0RC+I4H5igGE118jRh1w==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "xunit.core": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "KB4yGCxNqIVyekhJLXtKSEq6BaXVp/JO3mbGVE1hxypZTLEe7h+sTbAhpA+yZW2dPtXTuiW+C1B2oxxHEkrmOw==", + "resolved": "2.5.0", + "contentHash": "dnV0Mn2s1C0y2m33AylQyMkEyhBQsL4R0302kwSGiEGuY3JwzEmhTa9pnghyMRPliYSs4fXfkEAP+5bKXryGFg==", "dependencies": { - "xunit.extensibility.core": "[2.4.2]", - "xunit.extensibility.execution": "[2.4.2]" + "xunit.extensibility.core": "[2.5.0]", + "xunit.extensibility.execution": "[2.5.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "W1BoXTIN1C6kpVSMw25huSet25ky6IAQUNovu3zGOGN/jWnbgSoTyCrlIhmXSg0tH5nEf8q7h3OjNHOjyu5PfA==", + "resolved": "2.5.0", + "contentHash": "xRm6NIV3i7I+LkjsAJ91Xz2fxJm/oMEi2CYq1G5HlGTgcK1Zo2wNbLO6nKX1VG5FZzXibSdoLwr/MofVvh3mFA==", "dependencies": { "NETStandard.Library": "1.6.1", "xunit.abstractions": "2.0.3" @@ -2491,11 +2491,11 @@ }, "xunit.extensibility.execution": { "type": "Transitive", - "resolved": "2.4.2", - "contentHash": "CZmgcKkwpyo8FlupZdWpJCryrAOWLh1FBPG6gmVZuPQkGQsim/oL4PcP4nfrC2hHgXUFtluvaJ0Sp9PQKUMNpg==", + "resolved": "2.5.0", + "contentHash": "7+v2Bvp+1ew1iMGQVb1glICi8jcNdHbRUX6Ru0dmJBViGdjiS7kyqcX2VxleQhFbKNi+WF0an7/TeTXD283RlQ==", "dependencies": { "NETStandard.Library": "1.6.1", - "xunit.extensibility.core": "[2.4.2]" + "xunit.extensibility.core": "[2.5.0]" } }, "apiservice": { @@ -2532,6 +2532,7 @@ "Microsoft.Graph": "[4.37.0, )", "Microsoft.Identity.Client": "[4.52.0, )", "Microsoft.Identity.Web.TokenCache": "[2.7.0, )", + "Microsoft.Rest.ClientRuntime": "[2.3.24, )", "Microsoft.TeamFoundationServer.Client": "[19.219.0-preview, )", "Octokit": "[2.0.1, )", "OpenTelemetry.Api": "[1.5.0-rc.1, )", @@ -2558,7 +2559,7 @@ "Microsoft.NET.Test.Sdk": "[17.6.2, )", "Moq": "[4.18.4, )", "System.Security.Cryptography.Pkcs": "[7.0.2, )", - "xunit": "[2.4.1, )" + "xunit": "[2.5.0, )" } } } diff --git a/src/ApiService/Tests/OrmModelsTest.cs b/src/ApiService/Tests/OrmModelsTest.cs index e78e7e1638..1aa7d2d163 100644 --- a/src/ApiService/Tests/OrmModelsTest.cs +++ b/src/ApiService/Tests/OrmModelsTest.cs @@ -1,19 +1,40 @@ 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; +using Xunit; namespace Tests { + public class Arbitraries { - public class OrmGenerators { - public static Gen BaseEvent() { - return Gen.OneOf(new[] { + public static Arbitrary ArbPoolName() + => Arb.From(from name in Arb.Generate() + where PoolName.IsValid(name.Get) + select PoolName.Parse(name.Get)); + + public static Arbitrary ArbScalesetId() + => Arb.From(from name in Arb.Generate() + where ScalesetId.IsValid(name.Get) + select ScalesetId.Parse(name.Get)); + + public static Arbitrary> ReadOnlyList() + => Arb.Default.List().Convert(x => (IReadOnlyList)x, x => (List)x); + + public static Arbitrary ArbVersion() + //OneFuzz version uses 3 number version + => Arb.From(from v in Arb.Generate<(UInt16, UInt16, UInt16)>() + select new Version(v.Item1, v.Item2, v.Item3)); + + public static Arbitrary ArbUri() + => Arb.From(from address in Arb.Generate() + select new Uri($"https://{address.Item}:8080/")); + + public static Arbitrary ArbBaseEvent() + => Arb.From(Gen.OneOf(new[] { Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), Arb.Generate().Select(e => e as BaseEvent), @@ -32,788 +53,279 @@ public class OrmGenerators { 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.IsValid(name.Get) - select PoolName.Parse(name.Get); - - public static Gen ScalesetIdGen { get; } - = from name in Arb.Generate() - where ScalesetId.IsValid(name.Get) - select ScalesetId.Parse(name.Get); - - public static Gen RegionGen { get; } - = from name in Arb.Generate() - where Region.IsValid(name.Get) - select Region.Parse(name.Get); - - public static Gen Node { get; } - = from arg in Arb.Generate, Tuple>>() - from poolName in PoolNameGen - from scalesetId in Arb.Generate() - select new Node( - InitializedAt: arg.Item1.Item1, - PoolName: poolName, - PoolId: arg.Item1.Item3, - MachineId: arg.Item1.Item3, - State: arg.Item1.Item4, - ScalesetId: ScalesetId.Parse(scalesetId.ToString()), - Heartbeat: arg.Item2.Item1, - Version: arg.Item2.Item2, - ReimageRequested: arg.Item2.Item3, - DeleteRequested: arg.Item2.Item4, - DebugKeepNode: arg.Item2.Item5); - - 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.Parse(scalesetId.ToString()), - 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 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: new SecretAddress(new System.Uri("http://test")), - 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, - CreatedAt: arg.Item5 - ) - ); - } - - 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 => + })); + public static Arbitrary ArbDateOnly() + => Arb.From(from date in Arb.Generate() + select DateOnly.FromDateTime(date)); + + public static Arbitrary ArbDownloadableEventMessage() + => Arb.From(from eventId in Arb.Generate() + from ev in Arb.Generate() + from instanceId in Arb.Generate() + from instanceName in Arb.Generate() + from createdAt in Arb.Generate() + from sasUrl in Arb.Generate() + from expiresOn in Arb.Generate() + select new DownloadableEventMessage( + EventId: eventId, + EventType: ev.GetEventType(), + Event: ev, + InstanceId: instanceId, + InstanceName: instanceName, + CreatedAt: createdAt, + SasUrl: sasUrl, + ExpiresOn: expiresOn)); + + public static Arbitrary ArbEventMessage() + => Arb.From(from eventId in Arb.Generate() + from ev in Arb.Generate() + from instanceId in Arb.Generate() + from instanceName in Arb.Generate() + from createdAt in Arb.Generate() + select new EventMessage( + EventId: eventId, + EventType: ev.GetEventType(), + Event: ev, + InstanceId: instanceId, + InstanceName: instanceName, + CreatedAt: createdAt)); + + public static Arbitrary ArbNetworkConfig() + => Arb.From(from addressSpace in Arb.Generate() + from subnet in Arb.Generate() + select new NetworkConfig( + AddressSpace: addressSpace.Item.ToString(), + Subnet: subnet.Item.ToString())); + + public static Arbitrary ArbNetworkSecurityConfig() + => Arb.From(from tags in Arb.Generate() + from ips in Arb.Generate() + select 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: new SecretAddress(new Uri("http://test")), - - Heartbeat: arg.Item2.Item1, - EndTime: arg.Item2.Item2, - UserInfo: arg.Item2.Item3 - ) - ); - } - - public static Gen ImageReferenceGen { get; } = - Gen.Elements( + AllowedServiceTags: tags, + AllowedIps: ips.Select(ip => ip.Item.ToString()).ToArray())); + + public static Arbitrary ArbInstanceConfig() + => Arb.From(from instanceName in Arb.Generate() + from admins in Arb.Generate() + from allowedAadTenants in Arb.Generate() + from networkConfig in Arb.Generate() + from proxyNsgConfig in Arb.Generate() + from extensions in Arb.Generate() + from proxyVmSku in Arb.Generate>() + from requireAdminPrivileges in Arb.Generate() + from apiAccessRules in Arb.Generate?>() + from groupMembership in Arb.Generate?>() + from vmTags in Arb.Generate?>() + from vmssTags in Arb.Generate?>() + select new InstanceConfig( + InstanceName: instanceName, + Admins: admins, + AllowedAadTenants: allowedAadTenants, + NetworkConfig: networkConfig, + ProxyNsgConfig: proxyNsgConfig, + Extensions: extensions, + ProxyVmSku: proxyVmSku.Get, + RequireAdminPrivileges: requireAdminPrivileges, + ApiAccessRules: apiAccessRules, + GroupMembership: groupMembership, + VmTags: vmTags, + VmssTags: vmssTags)); + + public static Arbitrary WebhookMessageLog() + => Arb.From(from id in Arb.Generate() + from ev in Arb.Generate() + from instanceId in Arb.Generate() + from instanceName in Arb.Generate() + from webhookId in Arb.Generate() + from state in Arb.Generate() + from tryCount in Arb.Generate() + select new WebhookMessageLog( + EventId: id, + EventType: ev.GetEventType(), + Event: ev, + InstanceId: instanceId, + InstanceName: instanceName, + WebhookId: webhookId, + State: state, + TryCount: tryCount)); + + public static Arbitrary ArbImageReference() + => Arb.From(Gen.Elements( ImageReference.MustParse("Canonical:UbuntuServer:20.04-LTS:latest"), ImageReference.MustParse($"/subscriptions/{Guid.Empty}/resourceGroups/resource-group/providers/Microsoft.Compute/galleries/gallery/images/imageName"), - ImageReference.MustParse($"/subscriptions/{Guid.Empty}/resourceGroups/resource-group/providers/Microsoft.Compute/images/imageName")); - - public static Gen Scaleset { get; } - = from arg in Arb.Generate, - Tuple, - Tuple>>>() - from scalesetId in Arb.Generate() - from poolName in PoolNameGen - from region in RegionGen - from image in ImageReferenceGen - select new Scaleset( - PoolName: poolName, - ScalesetId: ScalesetId.Parse(scalesetId.ToString()), - State: arg.Item1.Item1, - Auth: new SecretAddress(new Uri("http://test")), - VmSku: arg.Item1.Item3, - Image: image, - 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, - CreatedAt: arg.Item6, - SasUrl: arg.Item7 - ) - ); - } - - public static Gen WebhookMessageEventGrid() { - var gen1 = Arb.Generate>(); - var gen2 = WebhookMessage(); - return gen1.Zip(gen2) - - .Select( - arg => - new WebhookMessageEventGrid( - DataVersion: arg.Item1.Item1, - Subject: arg.Item1.Item2, - EventType: arg.Item1.Item3.GetEventType(), - Data: arg.Item2, - Id: arg.Item1.Item4, - EventTime: arg.Item1.Item5 - ) - ); - } - - public static Gen Report() { - return Arb.Generate, Guid, int, Uri?>>().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, - ToolName: arg.Item1, - ToolVersion: arg.Item1, - OnefuzzVersion: arg.Item1, - ReportUrl: arg.Item6 - - ) - ); - } - - 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, - arg.Item3 - ) - ); - } - - 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.IsValid(nameString) - select Container.Parse(nameString); - - public static Gen AdoDuplicateTemplate() { - return Arb.Generate, Dictionary, string?>>().Select( - arg => - new ADODuplicateTemplate( - arg.Item1, - arg.Item2, - arg.Item2, - arg.Item3 - ) - ); - } - - public static Gen AdoTemplate() { - return Arb.Generate, NonEmptyString, List, Dictionary, ADODuplicateTemplate, string?>>().Select( - arg => - new AdoTemplate( - arg.Item1, - arg.Item2, - arg.Item3.Item, - arg.Item3.Item, - arg.Item4, - arg.Item5, - AdoDuplicateTemplate().Sample(1, 1).First(), - arg.Item7 - ) - ); - } - - public static Gen TeamsTemplate() { - return Arb.Generate>>().Select( - arg => - new TeamsTemplate( - arg.Item1 - ) - ); - } - - public static Gen GithubAuth() { - return Arb.Generate>().Select( - arg => - new GithubAuth( - arg.Item1, - arg.Item1 - ) - ); - } - - public static Gen GithubIssueSearch() { - return Arb.Generate, string, string?, GithubIssueState?>>().Select( - arg => - new GithubIssueSearch( - arg.Item1, - arg.Item2, - arg.Item3, - arg.Item4 - ) - ); - } - - public static Gen GithubIssuesTemplate() { - return Arb.Generate, NonEmptyString, GithubIssueSearch, List, GithubIssueDuplicate>>().Select( - arg => - new GithubIssuesTemplate( - arg.Item1, - arg.Item2.Item, - arg.Item2.Item, - arg.Item2.Item, - arg.Item2.Item, - arg.Item3, - arg.Item4, - arg.Item4, - arg.Item5 - ) - ); - } - - public static Gen NotificationTemplate() { - return Gen.OneOf(new[] { - AdoTemplate().Select(a => a as NotificationTemplate), - TeamsTemplate().Select(e => e as NotificationTemplate), - GithubIssuesTemplate().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 static Gen DownloadableEventMessage() { - return Arb.Generate>().Select( - arg => - new DownloadableEventMessage( - EventId: arg.Item1, - EventType: arg.Item2.GetEventType(), - Event: arg.Item2, - InstanceId: arg.Item3, - InstanceName: arg.Item4, - CreatedAt: arg.Item5, - SasUrl: arg.Item6, - ExpiresOn: arg.Item7 - ) - ); - } - - public static Gen DateOnly() { - return Arb.Generate>().Select( - arg => - System.DateOnly.FromDateTime(arg.Item1) - ); - } - } - - public class OrmArb { - - public static Arbitrary PoolName { get; } = OrmGenerators.PoolNameGen.ToArbitrary(); - public static Arbitrary ScalesetId { get; } = OrmGenerators.ScalesetIdGen.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 DateOnly() { - return Arb.From(OrmGenerators.DateOnly()); - } - - public static Arbitrary DownloadableEventMessage() { - return Arb.From(OrmGenerators.DownloadableEventMessage()); - } - - 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 ImageReference() - => Arb.From(OrmGenerators.ImageReferenceGen); - - 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; - } + ImageReference.MustParse($"/subscriptions/{Guid.Empty}/resourceGroups/resource-group/providers/Microsoft.Compute/images/imageName"))); + + public static Arbitrary WebhookMessage() + => Arb.From(from id in Arb.Generate() + from ev in Arb.Generate() + from instanceId in Arb.Generate() + from instanceName in Arb.Generate() + from webhookId in Arb.Generate() + from createdAt in Arb.Generate() + from sasUrl in Arb.Generate() + select new WebhookMessage( + EventId: id, + EventType: ev.GetEventType(), + Event: ev, + InstanceId: instanceId, + InstanceName: instanceName, + WebhookId: webhookId, + CreatedAt: createdAt, + SasUrl: sasUrl)); + + public static Arbitrary Report() + => Arb.From(from s in Arb.Generate() + from b in Arb.Generate() + from slist in Arb.Generate>() + from g in Arb.Generate() + from i in Arb.Generate() + from u in Arb.Generate() + select new Report( + InputUrl: s, + InputBlob: b, + Executable: s, + CrashType: s, + CrashSite: s, + CallStack: slist, + CallStackSha256: s, + InputSha256: s, + AsanLog: s, + TaskId: g, + JobId: g, + ScarinessScore: i, + ScarinessDescription: s, + MinimizedStack: slist, + MinimizedStackSha256: s, + MinimizedStackFunctionNames: slist, + MinimizedStackFunctionNamesSha256: s, + MinimizedStackFunctionLines: slist, + MinimizedStackFunctionLinesSha256: s, + ToolName: s, + ToolVersion: s, + OnefuzzVersion: s, + ReportUrl: u)); + + public static Arbitrary ArbContainer() + => Arb.From(from len in Gen.Choose(3, 63) + from name in Gen.ArrayOf(len, Gen.Elements("abcdefghijklmnopqrstuvwxyz0123456789-")) + let nameString = new string(name) + where Container.IsValid(nameString) + select Container.Parse(nameString)); + + public static Arbitrary ArbRegion() + => Arb.From(from name in Arb.Generate() + where Region.IsValid(name.Get) + select Region.Parse(name.Get)); + + public static Arbitrary ArbNotificationTemplate() + => Arb.From( + Gen.OneOf(new[] { + Arb.Generate().Select(a => a as NotificationTemplate), + Arb.Generate().Select(e => e as NotificationTemplate), + Arb.Generate().Select(e => e as NotificationTemplate) + })); + + public static Arbitrary ArbAdoTemplate() + => Arb.From(from baseUrl in Arb.Generate() + from authToken in Arb.Generate>() + from str in Arb.Generate() + from fields in Arb.Generate>() + from adoFields in Arb.Generate>() + from dupeTemplate in Arb.Generate() + select new AdoTemplate( + baseUrl, + authToken, + str.Get, + str.Get, + fields, + adoFields, + dupeTemplate, + str.Get)); + + public static Arbitrary ArbTeamsTemplate() + => Arb.From(from data in Arb.Generate>() + select new TeamsTemplate(data)); + + public static Arbitrary ArbGithubIssuesTemplate() + => Arb.From(from data in Arb.Generate>() + from str in Arb.Generate() + from search in Arb.Generate() + from assignees in Arb.Generate>() + from labels in Arb.Generate>() + from dupe in Arb.Generate() + select new GithubIssuesTemplate( + data, + str.Get, str.Get, str.Get, str.Get, + search, + assignees, + labels, + dupe)); + + public static Arbitrary ArbWebhookMessageEventGrid() + => Arb.From(from version in Arb.Generate() + from subject in Arb.Generate() + from id in Arb.Generate() + from eventTime in Arb.Generate() + from message in Arb.Generate() + select new WebhookMessageEventGrid( + DataVersion: version, + Subject: subject, + EventType: message.EventType, + Data: message, + Id: id, + EventTime: eventTime)); + + public static Arbitrary> ISecret() + => Arb.From(Gen.Constant>(new SecretAddress(new Uri("http://example.com")))); } public class OrmModelsTest { - EntityConverter _converter = new EntityConverter(new TestSecretOperations()); - ITestOutputHelper _output; + private readonly JsonSerializerOptions _opts = EntityConverter.GetJsonSerializerOptions(); + private readonly EntityConverter _converter = new(new TestSecretOperations()); - public OrmModelsTest(ITestOutputHelper output) { - _ = Arb.Register(); - _output = output; + public OrmModelsTest() { + _ = Arb.Register(); } - bool Test(T e) where T : EntityBase { - var v = _converter.ToTableEntity(e).Result; - var r = _converter.ToRecord(v); - return EqualityComparison.AreEqual(e, r); - + private void Test(T e) where T : EntityBase { + var r = _converter.ToRecord(_converter.ToTableEntity(e).Result); + // cheap way to compare objects: + var s1 = JsonSerializer.Serialize(e, _opts); + var s2 = JsonSerializer.Serialize(r, _opts); + Assert.Equal(s1, s2); } [Property] - public bool Node(Node node) { - return Test(node); - } + public void Node(Node node) => Test(node); [Property] - public bool ProxyForward(ProxyForward proxyForward) { - return Test(proxyForward); - } + public void ProxyForward(ProxyForward proxyForward) => Test(proxyForward); [Property] - public bool Proxy(Proxy proxy) { - return Test(proxy); - } + public void Proxy(Proxy proxy) => Test(proxy); [Property] - public bool Task(Task task) { - return Test(task); - } + public void Task(Task task) => Test(task); [Property] - public bool InstanceConfig(InstanceConfig cfg) { - return Test(cfg); - } + public void InstanceConfig(InstanceConfig cfg) => Test(cfg); [Property] - public bool Scaleset(Scaleset ss) { - return Test(ss); - } + public void Scaleset(Scaleset ss) => Test(ss); [Property] - public bool WebhookMessageLog(WebhookMessageLog log) { - return Test(log); - } + public void WebhookMessageLog(WebhookMessageLog log) => Test(log); [Property] - public bool Webhook(Webhook wh) { - return Test(wh); - } + public void Webhook(Webhook wh) => Test(wh); [Property] - public bool Notification(Notification n) { - return Test(n); - } + public void Notification(Notification n) => Test(n); [Property] - public bool Job(Job j) { - return Test(j); - } + public void Job(Job j) => Test(j); /* //Sample function on how repro a failing test run, using Replay @@ -830,329 +342,198 @@ void Replay() public class OrmJsonSerialization { + private readonly JsonSerializerOptions _opts = EntityConverter.GetJsonSerializerOptions(); - JsonSerializerOptions _opts = EntityConverter.GetJsonSerializerOptions(); - ITestOutputHelper _output; - - public OrmJsonSerialization(ITestOutputHelper output) { - _ = Arb.Register(); - _output = output; + public OrmJsonSerialization() { + _ = Arb.Register(); } - - 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); + void Test(T v) { + var s1 = JsonSerializer.Serialize(v, _opts); + var s2 = JsonSerializer.Serialize(JsonSerializer.Deserialize(s1, _opts), _opts); + Assert.Equal(s1, s2); } [Property] - public bool Node(Node node) { - return Test(node); - } + public void Node(Node node) => Test(node); [Property] - public bool ProxyForward(ProxyForward proxyForward) { - return Test(proxyForward); - } + public void ProxyForward(ProxyForward proxyForward) => Test(proxyForward); [Property] - public bool Proxy(Proxy proxy) { - return Test(proxy); - } + public void Proxy(Proxy proxy) => Test(proxy); [Property] - public bool Task(Task task) { - return Test(task); - } - + public void Task(Task task) => Test(task); [Property] - public bool InstanceConfig(InstanceConfig cfg) { - return Test(cfg); - } - + public void InstanceConfig(InstanceConfig cfg) => Test(cfg); [Property] - public bool Scaleset(Scaleset ss) { - return Test(ss); - } + public void Scaleset(Scaleset ss) => Test(ss); [Property] - public bool WebhookMessageLog(WebhookMessageLog log) { - return Test(log); - } + public void WebhookMessageLog(WebhookMessageLog log) => Test(log); [Property] - public bool Webhook(Webhook wh) { - return Test(wh); - } + public void Webhook(Webhook wh) => Test(wh); [Property] - public bool WebhookMessageEventGrid(WebhookMessageEventGrid evt) { - return Test(evt); - } - + public void WebhookMessageEventGrid(WebhookMessageEventGrid evt) => Test(evt); [Property] - public bool WebhookMessage(WebhookMessage msg) { - return Test(msg); - } - + public void WebhookMessage(WebhookMessage msg) => Test(msg); [Property] - public bool TaskHeartbeatEntry(TaskHeartbeatEntry e) { - return Test(e); - } + public void TaskHeartbeatEntry(TaskHeartbeatEntry e) => Test(e); [Property] - public bool NodeCommand(NodeCommand e) { - return Test(e); - } + public void NodeCommand(NodeCommand e) => Test(e); [Property] - public bool NodeTasks(NodeTasks e) { - return Test(e); - } + public void NodeTasks(NodeTasks e) => Test(e); [Property] - public bool ProxyHeartbeat(ProxyHeartbeat e) { - return Test(e); - } + public void ProxyHeartbeat(ProxyHeartbeat e) => Test(e); [Property] - public bool ProxyConfig(ProxyConfig e) { - return Test(e); - } + public void ProxyConfig(ProxyConfig e) => Test(e); [Property] - public bool TaskDetails(TaskDetails e) { - return Test(e); - } + public void TaskDetails(TaskDetails e) => Test(e); [Property] - public bool TaskVm(TaskVm e) { - return Test(e); - } + public void TaskVm(TaskVm e) => Test(e); [Property] - public bool TaskPool(TaskPool e) { - return Test(e); - } + public void TaskPool(TaskPool e) => Test(e); [Property] - public bool TaskContainers(TaskContainers e) { - return Test(e); - } + public void TaskContainers(TaskContainers e) => Test(e); [Property] - public bool TaskConfig(TaskConfig e) { - return Test(e); - } + public void TaskConfig(TaskConfig e) => Test(e); [Property] - public bool TaskEventSummary(TaskEventSummary e) { - return Test(e); - } + public void TaskEventSummary(TaskEventSummary e) => Test(e); [Property] - public bool NodeAssignment(NodeAssignment e) { - return Test(e); - } + public void NodeAssignment(NodeAssignment e) => Test(e); [Property] - public bool KeyvaultExtensionConfig(KeyvaultExtensionConfig e) { - return Test(e); - } + public void KeyvaultExtensionConfig(KeyvaultExtensionConfig e) => Test(e); [Property] - public bool AzureMonitorExtensionConfig(AzureMonitorExtensionConfig e) { - return Test(e); - } + public void AzureMonitorExtensionConfig(AzureMonitorExtensionConfig e) => Test(e); [Property] - public bool AzureVmExtensionConfig(AzureVmExtensionConfig e) { - return Test(e); - } + public void AzureVmExtensionConfig(AzureVmExtensionConfig e) => Test(e); [Property] - public bool NetworkConfig(NetworkConfig e) { - return Test(e); - } + public void NetworkConfig(NetworkConfig e) => Test(e); [Property] - public bool NetworkSecurityGroupConfig(NetworkSecurityGroupConfig e) { - return Test(e); - } + public void NetworkSecurityGroupConfig(NetworkSecurityGroupConfig e) => Test(e); [Property] - public bool Report(Report e) { - return Test(e); - } + public void Report(Report e) => Test(e); [Property] - public bool Notification(Notification e) { - return Test(e); - } + public void Notification(Notification e) => Test(e); [Property] - public bool NoReproReport(NoReproReport e) { - return Test(e); - } + public void NoReproReport(NoReproReport e) => Test(e); [Property] - public bool CrashTestResult(CrashTestResult e) { - return Test(e); - } + public void CrashTestResult(CrashTestResult e) => Test(e); [Property] - public bool NotificationTemplate(NotificationTemplate e) { - return Test(e); - } + public void NotificationTemplate(NotificationTemplate e) => Test(e); [Property] - public bool RegressionReport(RegressionReport e) { - return Test(e); - } + public void RegressionReport(RegressionReport e) => Test(e); [Property] - public bool Job(Job e) { - return Test(e); - } + public void Job(Job e) => Test(e); [Property] - public bool EventNodeHeartbeat(EventNodeHeartbeat e) { - return Test(e); - } + public void EventNodeHeartbeat(EventNodeHeartbeat e) => Test(e); [Property] - public bool EventTaskHeartbeat(EventTaskHeartbeat e) { - return Test(e); - } + public void EventTaskHeartbeat(EventTaskHeartbeat e) => Test(e); [Property] - public bool EventTaskStopped(EventTaskStopped e) { - return Test(e); - } + public void EventTaskStopped(EventTaskStopped e) => Test(e); [Property] - public bool EventInstanceConfigUpdated(EventInstanceConfigUpdated e) { - return Test(e); - } + public void EventInstanceConfigUpdated(EventInstanceConfigUpdated e) => Test(e); [Property] - public bool EventProxyCreated(EventProxyCreated e) { - return Test(e); - } + public void EventProxyCreated(EventProxyCreated e) => Test(e); [Property] - public bool EventProxyDeleted(EventProxyDeleted e) { - return Test(e); - } + public void EventProxyDeleted(EventProxyDeleted e) => Test(e); [Property] - public bool EventProxyFailed(EventProxyFailed e) { - return Test(e); - } + public void EventProxyFailed(EventProxyFailed e) => Test(e); [Property] - public bool EventProxyStateUpdated(EventProxyStateUpdated e) { - return Test(e); - } + public void EventProxyStateUpdated(EventProxyStateUpdated e) => Test(e); [Property] - public bool EventCrashReported(EventCrashReported e) { - return Test(e); - } + public void EventCrashReported(EventCrashReported e) => Test(e); [Property] - public bool EventRegressionReported(EventRegressionReported e) { - return Test(e); - } + public void EventRegressionReported(EventRegressionReported e) => Test(e); [Property] - public bool EventFileAdded(EventFileAdded e) { - return Test(e); - } + public void EventFileAdded(EventFileAdded e) => Test(e); [Property] - public bool EventTaskFailed(EventTaskFailed e) { - return Test(e); - } + public void EventTaskFailed(EventTaskFailed e) => Test(e); [Property] - public bool EventTaskStateUpdated(EventTaskStateUpdated e) { - return Test(e); - } + public void EventTaskStateUpdated(EventTaskStateUpdated e) => Test(e); [Property] - public bool EventScalesetFailed(EventScalesetFailed e) { - return Test(e); - } - + public void EventScalesetFailed(EventScalesetFailed e) => Test(e); [Property] - public bool EventScalesetResizeScheduled(EventScalesetResizeScheduled e) { - return Test(e); - } - + public void EventScalesetResizeScheduled(EventScalesetResizeScheduled e) => Test(e); [Property] - public bool EventScalesetStateUpdated(EventScalesetStateUpdated e) { - return Test(e); - } + public void EventScalesetStateUpdated(EventScalesetStateUpdated e) => Test(e); [Property] - public bool EventNodeDeleted(EventNodeDeleted e) { - return Test(e); - } + public void EventNodeDeleted(EventNodeDeleted e) => Test(e); [Property] - public bool EventNodeCreated(EventNodeCreated e) { - return Test(e); - } + public void EventNodeCreated(EventNodeCreated e) => Test(e); [Property] - public bool EventMessage(DownloadableEventMessage e) { - return Test(e); - } + public void EventMessage(DownloadableEventMessage e) => Test(e); [Property] - public bool Error(Error e) { - return Test(e); - } + public void Error(Error e) => Test(e); [Property] - public bool Container(Container c) { - return Test(c); - } - - + public void Container(Container c) => 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(811038773, 297085737); var p = Prop.ForAll((NotificationTemplate x) => NotificationTemplate(x)); p.Check(new Configuration { Replay = seed }); } + */ } - } diff --git a/src/ApiService/Tests/OrmTest.cs b/src/ApiService/Tests/OrmTest.cs index f85222ff53..2e77560caa 100644 --- a/src/ApiService/Tests/OrmTest.cs +++ b/src/ApiService/Tests/OrmTest.cs @@ -466,7 +466,7 @@ sealed record TestKeyGetter([PartitionKey] Guid PartitionKey, [RowKey] Guid RowK sealed record NestedEntity( [PartitionKey] int Id, [RowKey] string TheName, - [property: TypeDiscrimnatorAttribute("EventType", typeof(EventTypeProvider))] + [property: TypeDiscrimnator("EventType", typeof(EventTypeProvider))] [property: JsonConverter(typeof(BaseEventConverter))] Nested? EventType ) : EntityBase(); diff --git a/src/ApiService/Tests/TemplateTests.cs b/src/ApiService/Tests/TemplateTests.cs index 9da5d1ecc2..97d8641eb8 100644 --- a/src/ApiService/Tests/TemplateTests.cs +++ b/src/ApiService/Tests/TemplateTests.cs @@ -223,8 +223,7 @@ public class TemplateTests { "Test build", 1, null - ) - ); + ), + null); } - } diff --git a/src/ApiService/Tests/Tests.csproj b/src/ApiService/Tests/Tests.csproj index 241296e07d..0253d76753 100644 --- a/src/ApiService/Tests/Tests.csproj +++ b/src/ApiService/Tests/Tests.csproj @@ -10,13 +10,13 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/ApiService/Tests/packages.lock.json b/src/ApiService/Tests/packages.lock.json index 05be7de507..4e148f11fc 100644 --- a/src/ApiService/Tests/packages.lock.json +++ b/src/ApiService/Tests/packages.lock.json @@ -4,9 +4,9 @@ "net7.0": { "coverlet.collector": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "xjY8xBigSeWIYs4I7DgUHqSNoGqnHi7Fv7/7RZD02rvZyG3hlsjnQKiVKVWKgr9kRKgmV+dEfu8KScvysiC0Wg==" + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "tW3lsNS+dAEII6YGUX/VMoJjBS1QvsxqJeqLaJXub08y1FSjasFPtQ4UBUsudE9PNrzLjooClMsPtY2cZLdXpQ==" }, "FluentAssertions": { "type": "Direct", @@ -66,20 +66,20 @@ }, "xunit": { "type": "Direct", - "requested": "[2.4.1, )", - "resolved": "2.4.1", - "contentHash": "XNR3Yz9QTtec16O0aKcO6+baVNpXmOnPUxDkCY97J+8krUYxPvXT1szYYEUdKk4sB8GOI2YbAjRIOm8ZnXRfzQ==", + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "f2V5wuAdoaq0mRTt9UBmPbVex9HcwFYn+y7WaKUz5Xpakcrv7lhtQWBJUWNY4N3Z+o+atDBLyAALM1QWx04C6Q==", "dependencies": { - "xunit.analyzers": "0.10.0", - "xunit.assert": "[2.4.1]", - "xunit.core": "[2.4.1]" + "xunit.analyzers": "1.2.0", + "xunit.assert": "2.5.0", + "xunit.core": "[2.5.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[2.4.3, )", - "resolved": "2.4.3", - "contentHash": "kZZSmOmKA8OBlAJaquPXnJJLM9RwQ27H7BMVqfMLUcTi9xHinWGJiWksa3D4NEtz0wZ/nxd2mogObvBgJKCRhQ==" + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "+Gp9vuC2431yPyKB15YrOTxCuEAErBQUTIs6CquumX1F073UaPHGW0VE/XVJLMh9W4sXdz3TBkcHdFWZrRn2Hw==" }, "Azure.Core": { "type": "Transitive", @@ -1077,8 +1077,8 @@ }, "Microsoft.Rest.ClientRuntime": { "type": "Transitive", - "resolved": "2.3.20", - "contentHash": "bw/H1nO4JdnhTagPHWIFQwtlQ6rb2jqw5RTrqPsPqzrjhJxc7P6MyNGdf4pgHQdzdpBSNOfZTEQifoUkxmzYXQ==", + "resolved": "2.3.24", + "contentHash": "hZH7XgM3eV2jFrnq7Yf0nBD4WVXQzDrer2gEY7HMNiwio2hwDsTHO6LWuueNQAfRpNp4W7mKxcXpwXUiuVIlYw==", "dependencies": { "Newtonsoft.Json": "10.0.3" } @@ -2462,30 +2462,30 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "0.10.0", - "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" + "resolved": "1.2.0", + "contentHash": "d3dehV/DASLRlR8stWQmbPPjfYC2tct50Evav+OlsJMkfFqkhYvzO1k0s81lk0px8O0knZU/FqC8SqbXOtn+hw==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.4.1", - "contentHash": "O/Oe0BS5RmSsM+LQOb041TzuPo5MdH2Rov+qXGS37X+KFG1Hxz7kopYklM5+1Y+tRGeXrOx5+Xne1RuqLFQoyQ==", + "resolved": "2.5.0", + "contentHash": "wN84pKX5jzfpgJ0bB6arrCA/oelBeYLCpnQ9Wj5xGEVPydKzVSDY5tEatFLHE/rO0+0RC+I4H5igGE118jRh1w==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "xunit.core": { "type": "Transitive", - "resolved": "2.4.1", - "contentHash": "Zsj5OMU6JasNGERXZy8s72+pcheG6Q15atS5XpZXqAtULuyQiQ6XNnUsp1gyfC6WgqScqMvySiEHmHcOG6Eg0Q==", + "resolved": "2.5.0", + "contentHash": "dnV0Mn2s1C0y2m33AylQyMkEyhBQsL4R0302kwSGiEGuY3JwzEmhTa9pnghyMRPliYSs4fXfkEAP+5bKXryGFg==", "dependencies": { - "xunit.extensibility.core": "[2.4.1]", - "xunit.extensibility.execution": "[2.4.1]" + "xunit.extensibility.core": "[2.5.0]", + "xunit.extensibility.execution": "[2.5.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.4.1", - "contentHash": "yKZKm/8QNZnBnGZFD9SewkllHBiK0DThybQD/G4PiAmQjKtEZyHi6ET70QPU9KtSMJGRYS6Syk7EyR2EVDU4Kg==", + "resolved": "2.5.0", + "contentHash": "xRm6NIV3i7I+LkjsAJ91Xz2fxJm/oMEi2CYq1G5HlGTgcK1Zo2wNbLO6nKX1VG5FZzXibSdoLwr/MofVvh3mFA==", "dependencies": { "NETStandard.Library": "1.6.1", "xunit.abstractions": "2.0.3" @@ -2493,11 +2493,11 @@ }, "xunit.extensibility.execution": { "type": "Transitive", - "resolved": "2.4.1", - "contentHash": "7e/1jqBpcb7frLkB6XDrHCGXAbKN4Rtdb88epYxCSRQuZDRW8UtTfdTEVpdTl8s4T56e07hOBVd4G0OdCxIY2A==", + "resolved": "2.5.0", + "contentHash": "7+v2Bvp+1ew1iMGQVb1glICi8jcNdHbRUX6Ru0dmJBViGdjiS7kyqcX2VxleQhFbKNi+WF0an7/TeTXD283RlQ==", "dependencies": { "NETStandard.Library": "1.6.1", - "xunit.extensibility.core": "[2.4.1]" + "xunit.extensibility.core": "[2.5.0]" } }, "apiservice": { @@ -2534,6 +2534,7 @@ "Microsoft.Graph": "[4.37.0, )", "Microsoft.Identity.Client": "[4.52.0, )", "Microsoft.Identity.Web.TokenCache": "[2.7.0, )", + "Microsoft.Rest.ClientRuntime": "[2.3.24, )", "Microsoft.TeamFoundationServer.Client": "[19.219.0-preview, )", "Octokit": "[2.0.1, )", "OpenTelemetry.Api": "[1.5.0-rc.1, )", diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock index 969a54c68f..51458037e3 100644 --- a/src/agent/Cargo.lock +++ b/src/agent/Cargo.lock @@ -1629,9 +1629,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.30.0" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28491f7753051e5704d4d0ae7860d45fae3238d7d235bc4289dcd45c48d3cec3" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" dependencies = [ "console", "globset", @@ -2289,6 +2289,7 @@ dependencies = [ "cobertura", "coverage", "crossterm", + "debuggable-module", "env_logger", "flexi_logger", "flume", diff --git a/src/agent/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader.csproj b/src/agent/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader.csproj index 5554e45f83..80b629e517 100644 --- a/src/agent/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader.csproj +++ b/src/agent/LibFuzzerDotnetLoader/LibFuzzerDotnetLoader.csproj @@ -6,6 +6,12 @@ net7.0 enable enable + + true + true + true + false + diff --git a/src/agent/onefuzz-task/Cargo.toml b/src/agent/onefuzz-task/Cargo.toml index 0d2b2e6b5e..0b85d4c16c 100644 --- a/src/agent/onefuzz-task/Cargo.toml +++ b/src/agent/onefuzz-task/Cargo.toml @@ -18,6 +18,7 @@ backoff = { version = "0.4", features = ["tokio"] } clap = { version = "4", features = ["cargo", "string"] } cobertura = { path = "../cobertura" } coverage = { path = "../coverage" } +debuggable-module = { path = "../debuggable-module" } crossterm = "0.26" env_logger = "0.10" flume = "0.10" diff --git a/src/agent/onefuzz-task/src/tasks/coverage/generic.rs b/src/agent/onefuzz-task/src/tasks/coverage/generic.rs index e6ebb5dc7a..b4d56b4579 100644 --- a/src/agent/onefuzz-task/src/tasks/coverage/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/coverage/generic.rs @@ -5,15 +5,20 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use std::sync::Arc; use std::time::Duration; use anyhow::{bail, Context, Result}; use async_trait::async_trait; use cobertura::CoberturaCoverage; use coverage::allowlist::{AllowList, TargetAllowList}; -use coverage::binary::BinaryCoverage; +use coverage::binary::{BinaryCoverage, DebugInfoCache}; use coverage::record::CoverageRecorder; use coverage::source::{binary_to_source_coverage, SourceCoverage}; +use debuggable_module::load_module::LoadModule; +use debuggable_module::loader::Loader; +use debuggable_module::path::FilePath; +use debuggable_module::Module; use onefuzz::env::LD_LIBRARY_PATH; use onefuzz::expand::{Expand, PlaceHolder}; use onefuzz::syncdir::SyncedDir; @@ -106,8 +111,25 @@ impl CoverageTask { }; let allowlist = self.load_target_allowlist().await?; + let heartbeat = self.config.common.init_heartbeat(None).await?; - let mut context = TaskContext::new(&self.config, coverage, allowlist, heartbeat); + + let mut seen_inputs = false; + + let target_exe_path = + try_resolve_setup_relative_path(&self.config.common.setup_dir, &self.config.target_exe) + .await?; + let target_exe = target_exe_path + .to_str() + .ok_or_else(|| anyhow::format_err!("target_exe path is not valid unicode"))?; + + let mut context = TaskContext::new( + &self.config, + coverage, + allowlist, + heartbeat, + target_exe.to_string(), + )?; if !context.uses_input() { bail!("input is not specified on the command line or arguments for the target"); @@ -115,8 +137,6 @@ impl CoverageTask { context.heartbeat.alive(); - let mut seen_inputs = false; - for dir in &self.config.readonly_inputs { debug!("recording coverage for {}", dir.local_path.display()); @@ -191,6 +211,7 @@ struct TaskContext<'a> { coverage: BinaryCoverage, allowlist: TargetAllowList, heartbeat: Option, + cache: Arc, } impl<'a> TaskContext<'a> { @@ -199,13 +220,26 @@ impl<'a> TaskContext<'a> { coverage: BinaryCoverage, allowlist: TargetAllowList, heartbeat: Option, - ) -> Self { - Self { + target_exe: String, + ) -> Result { + let cache = DebugInfoCache::new(allowlist.source_files.clone()); + let loader = Loader::new(); + + // Preload the cache with the target executable, to avoid counting debuginfo analysis + // time against the exeuction timeout for the first iteration. + let module: Box = LoadModule::load(&loader, FilePath::new(target_exe)?)?; + + cache + .get_or_insert(&*module) + .context("Failed to load debuginfo for target_exe when populating DebugInfoCache")?; + + Ok(Self { config, coverage, allowlist, heartbeat, - } + cache: Arc::new(cache), + }) } pub async fn record_input(&mut self, input: &Path) -> Result<()> { @@ -254,8 +288,10 @@ impl<'a> TaskContext<'a> { let allowlist = self.allowlist.clone(); let cmd = self.command_for_input(input).await?; let timeout = self.config.timeout(); + let cache = self.cache.clone(); let recorded = spawn_blocking(move || { CoverageRecorder::new(cmd) + .debuginfo_cache(cache) .allowlist(allowlist) .timeout(timeout) .record() diff --git a/src/agent/stacktrace-parser/Cargo.toml b/src/agent/stacktrace-parser/Cargo.toml index 773c2471f1..cbe70fa020 100644 --- a/src/agent/stacktrace-parser/Cargo.toml +++ b/src/agent/stacktrace-parser/Cargo.toml @@ -16,5 +16,5 @@ serde_json = "1.0" libclusterfuzz = { path = "../libclusterfuzz" } [dev-dependencies] -insta = { version = "1.30.0", features = ["glob", "json"] } +insta = { version = "1.31.0", features = ["glob", "json"] } pretty_assertions = "1.4" diff --git a/src/ci/dotnet-fuzzing-tools.ps1 b/src/ci/dotnet-fuzzing-tools.ps1 index c6ba756655..95c1c7fa1d 100644 --- a/src/ci/dotnet-fuzzing-tools.ps1 +++ b/src/ci/dotnet-fuzzing-tools.ps1 @@ -26,7 +26,7 @@ popd # Build SharpFuzz and our dynamic loader harness for `libfuzzer-dotnet`. pushd src/agent/LibFuzzerDotnetLoader -dotnet publish . -c Release -o $ARTIFACTS/LibFuzzerDotnetLoader --sc -r win10-x64 +dotnet publish . -c Release -o $ARTIFACTS/LibFuzzerDotnetLoader -r win10-x64 if ($LASTEXITCODE -ne 0) { throw "dotnet publish exited with $LASTEXITCODE" } popd diff --git a/src/ci/dotnet-fuzzing-tools.sh b/src/ci/dotnet-fuzzing-tools.sh index 14e199b7fd..8395b14ce1 100755 --- a/src/ci/dotnet-fuzzing-tools.sh +++ b/src/ci/dotnet-fuzzing-tools.sh @@ -34,7 +34,7 @@ popd # Build SharpFuzz and our dynamic loader harness for `libfuzzer-dotnet`. pushd src/agent/LibFuzzerDotnetLoader -dotnet publish . -c Release -o $ARTIFACTS/LibFuzzerDotnetLoader --sc -r linux-x64 +dotnet publish . -c Release -o $ARTIFACTS/LibFuzzerDotnetLoader -r linux-x64 popd # Build `libfuzzer-dotnet`. diff --git a/src/utils/check-pr/check-pr.py b/src/utils/check-pr/check-pr.py index ff79e2fa65..52dd1417f8 100755 --- a/src/utils/check-pr/check-pr.py +++ b/src/utils/check-pr/check-pr.py @@ -133,7 +133,7 @@ def register(self) -> None: time.sleep(30) return - def test(self, filename: str) -> None: + def test(self, filenames: List[str]) -> None: venv = "test-venv" subprocess.check_call(f"python -mvenv {venv}", shell=True) py = venv_path(venv, "python") @@ -150,8 +150,16 @@ def test(self, filename: str) -> None: commands = [ ( - "extracting integration-test-artifacts", + f"extracting {filename}", f"unzip -qq {filename} -d {test_dir}", + ) + for filename in filenames + ] + + commands += [ + ( + "extracting integration test artifacts", + f"unzip -qq {filenames} -d {test_dir}", ), ("test venv", f"python -mvenv {venv}"), ("installing wheel", f"./{venv}/bin/pip install -q wheel"), @@ -166,6 +174,7 @@ def test(self, filename: str) -> None: ), ), ] + for msg, cmd in commands: print(msg) print(cmd) @@ -194,20 +203,30 @@ def run(self, *, merge_on_success: bool = False) -> None: release_filename, ) - test_filename = "integration-test-artifacts.zip" + windows_test_filename = "artifact-integration-tests-windows.zip" + self.downloader.get_artifact( + self.repo, + "ci.yml", + self.branch, + self.pr, + "artifact-integration-tests-windows", + windows_test_filename, + ) + + linux_test_filename = "artifact-integration-tests-linux.zip" self.downloader.get_artifact( self.repo, "ci.yml", self.branch, self.pr, - "integration-test-artifacts", - test_filename, + "artifact-integration-tests-linux", + linux_test_filename, ) self.deploy(release_filename) if not self.skip_tests: - self.test(test_filename) + self.test([windows_test_filename, linux_test_filename]) if merge_on_success: self.merge() diff --git a/src/utils/check-pr/github_client.py b/src/utils/check-pr/github_client.py index 26107237bf..5b6919bdaf 100644 --- a/src/utils/check-pr/github_client.py +++ b/src/utils/check-pr/github_client.py @@ -150,7 +150,6 @@ def download_artifacts( update_branch: bool = True, ) -> None: release_filename = "release-artifacts.zip" - downloader.get_artifact( repo, "ci.yml", @@ -160,13 +159,23 @@ def download_artifacts( os.path.join(directory, release_filename), ) - test_filename = "integration-test-artifacts.zip" + test_filename = "artifact-integration-tests-linux.zip" + downloader.get_artifact( + repo, + "ci.yml", + branch, + pr, + "artifact-integration-tests-linux", + os.path.join(directory, test_filename), + ) + + test_filename = "artifact-integration-tests-windows.zip" downloader.get_artifact( repo, "ci.yml", branch, pr, - "integration-test-artifacts", + "artifact-integration-tests-windows", os.path.join(directory, test_filename), )