Skip to content

Commit 780c0ec

Browse files
chiangvincentVincent Chiang
andauthored
Add compile errors to the functionerror collection (#8518)
* add compile errors to the functionerror collection, which is used in admin/functions/{name}/status endpoint * add unit test for functionerrors collection in dotnetinvocation worker * remove host.json from pr * change functionErrorCollection into hashset Co-authored-by: Vincent Chiang <vchiang@microsoft.com>
1 parent 2e7f9df commit 780c0ec

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

src/WebJobs.Script/Description/DotNet/DotNetFunctionInvoker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ internal async Task<MethodInfo> GetFunctionTargetAsync(bool isInvocation = false
197197
// on the invocation path we want to log detailed logs and all compilation diagnostics
198198
var properties = isInvocation ? null : PrimaryHostLogProperties;
199199
FunctionLogger.Log(LogLevel.Error, 0, properties, exc, (state, ex) => "Function compilation error");
200+
Utility.AddFunctionError(Host.FunctionErrors, _functionMetadata.Name, exc.ToString());
200201
TraceCompilationDiagnostics(exc.Diagnostics, LogTargets.User, isInvocation);
201202
throw;
202203
}

src/WebJobs.Script/Utility.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,10 +541,10 @@ internal static void AddFunctionError(IDictionary<string, ICollection<string>> f
541541
{
542542
functionName = isFunctionShortName ? functionName : Utility.GetFunctionShortName(functionName);
543543

544-
ICollection<string> functionErrorCollection = new Collection<string>();
544+
ICollection<string> functionErrorCollection = new HashSet<string>();
545545
if (!string.IsNullOrEmpty(functionName) && !functionErrors.TryGetValue(functionName, out functionErrorCollection))
546546
{
547-
functionErrors[functionName] = functionErrorCollection = new Collection<string>();
547+
functionErrors[functionName] = functionErrorCollection = new HashSet<string>();
548548
}
549549
functionErrorCollection.Add(error);
550550
}

test/WebJobs.Script.Tests/Description/DotNet/DotNetFunctionInvokerTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,61 @@ public async Task RestorePackagesAsync_WithUpdatedReferences_TriggersShutdown(bo
409409
}
410410
}
411411

412+
[Fact]
413+
public async Task GetFunctionTargetAsync_CompilationError_ReportsResultsToScriptHostFunctionErrors()
414+
{
415+
// Create the compilation exception we expect to throw
416+
var descriptor = new DiagnosticDescriptor(DotNetConstants.MissingFunctionEntryPointCompilationCode,
417+
"Test compilation exception", "Test compilation error", "AzureFunctions", DiagnosticSeverity.Error, true);
418+
var exception = new CompilationErrorException("Test compilation exception", ImmutableArray.Create(Diagnostic.Create(descriptor, Location.None)));
419+
420+
// Create the invoker dependencies and setup the appropriate method to throw the exception
421+
RunDependencies dependencies = CreateDependencies();
422+
dependencies.Compilation.Setup(c => c.GetEntryPointSignature(It.IsAny<IFunctionEntryPointResolver>(), It.IsAny<Assembly>()))
423+
.Throws(exception);
424+
dependencies.Compilation.Setup(c => c.EmitAsync(It.IsAny<CancellationToken>()))
425+
.ReturnsAsync(DotNetCompilationResult.FromPath(typeof(DotNetFunctionInvokerTests).Assembly.Location));
426+
427+
string functionName = Guid.NewGuid().ToString();
428+
string rootFunctionsFolder = Path.Combine(Path.GetTempPath(), functionName);
429+
Directory.CreateDirectory(rootFunctionsFolder);
430+
431+
// Create a dummy file to represent our function
432+
string filePath = Path.Combine(rootFunctionsFolder, Guid.NewGuid().ToString() + ".csx");
433+
File.WriteAllText(filePath, string.Empty);
434+
435+
var metadata = new FunctionMetadata
436+
{
437+
ScriptFile = filePath,
438+
FunctionDirectory = Path.GetDirectoryName(filePath),
439+
Name = functionName,
440+
Language = DotNetScriptTypes.CSharp
441+
};
442+
443+
metadata.Bindings.Add(new BindingMetadata() { Name = "Test", Type = "ManualTrigger" });
444+
445+
var invoker = new DotNetFunctionInvoker(dependencies.Host, metadata, new Collection<FunctionBinding>(), new Collection<FunctionBinding>(),
446+
dependencies.EntrypointResolver.Object, dependencies.CompilationServiceFactory.Object, dependencies.LoggerFactory, dependencies.MetricsLogger,
447+
new Collection<IScriptBindingProvider>());
448+
449+
// Send file change notification to trigger a reload
450+
var fileEventArgs = new FileSystemEventArgs(WatcherChangeTypes.Changed, Path.GetTempPath(), Path.Combine(Path.GetFileName(rootFunctionsFolder), Path.GetFileName(filePath)));
451+
dependencies.Host.EventManager.Publish(new FileEvent(EventSources.ScriptFiles, fileEventArgs));
452+
453+
await Assert.ThrowsAsync<CompilationErrorException>(async () =>
454+
{
455+
await invoker.GetFunctionTargetAsync();
456+
});
457+
458+
dependencies.Host.FunctionErrors.TryGetValue(functionName, out ICollection<string> functionErrors);
459+
Console.WriteLine(functionErrors);
460+
Assert.True(functionErrors.Any());
461+
foreach (string error in functionErrors)
462+
{
463+
Assert.StartsWith("Microsoft.CodeAnalysis.Scripting.CompilationErrorException: Test compilation exception", error);
464+
}
465+
}
466+
412467
// TODO: DI (FACAVAL) Use test helpers to create host and inject services
413468
private RunDependencies CreateDependencies(Action<IServiceCollection> configureServices = null)
414469
{

0 commit comments

Comments
 (0)