using System.Collections.Generic; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; using StellaOps.Scanner.Analyzers.Lang.Java.Internal; using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ClassPath; using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ServiceProviders; using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; using Xunit; namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests; public sealed class JavaServiceProviderScannerTests { [Fact] public void Scan_SelectsFirstProviderByClasspathOrder() { var root = TestPaths.CreateTemporaryDirectory(); try { var servicesA = new Dictionary { ["java.sql.Driver"] = new[] { "com.example.ADriver" }, }; var servicesB = new Dictionary { ["java.sql.Driver"] = new[] { "com.example.BDriver" }, }; CreateJarWithClasses(root, "libs/a.jar", new[] { "com.example.ADriver" }, servicesA); CreateJarWithClasses(root, "libs/b.jar", new[] { "com.example.BDriver" }, servicesB); var cancellationToken = CancellationToken.None; var context = new LanguageAnalyzerContext(root, TimeProvider.System); var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken); var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken); var analysis = JavaServiceProviderScanner.Scan(classPath, JavaSpiCatalog.Default, cancellationToken); var service = Assert.Single(analysis.Services, record => record.ServiceId == "java.sql.Driver"); Assert.Equal("jdk", service.Category); var selected = Assert.Single(service.Candidates.Where(candidate => candidate.IsSelected)); Assert.Equal("com.example.ADriver", selected.ProviderClass); Assert.Empty(service.Warnings); } finally { TestPaths.SafeDelete(root); } } [Fact] public void Scan_FlagsDuplicateProviders() { var root = TestPaths.CreateTemporaryDirectory(); try { var services = new Dictionary { ["java.sql.Driver"] = new[] { "com.example.DuplicateDriver" }, }; CreateJarWithClasses(root, "libs/a.jar", new[] { "com.example.DuplicateDriver" }, services); CreateJarWithClasses(root, "libs/b.jar", new[] { "com.example.Other" }, services); var cancellationToken = CancellationToken.None; var context = new LanguageAnalyzerContext(root, TimeProvider.System); var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken); var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken); var analysis = JavaServiceProviderScanner.Scan(classPath, JavaSpiCatalog.Default, cancellationToken); var service = Assert.Single(analysis.Services, record => record.ServiceId == "java.sql.Driver"); Assert.NotEmpty(service.Warnings); Assert.Contains(service.Warnings, warning => warning.Contains("duplicate-provider", StringComparison.OrdinalIgnoreCase)); } finally { TestPaths.SafeDelete(root); } } [Fact] public void Scan_RespectsBootFatJarOrdering() { var root = TestPaths.CreateTemporaryDirectory(); try { JavaFixtureBuilder.CreateSpringBootFatJar(root, "apps/app-fat.jar"); var cancellationToken = CancellationToken.None; var context = new LanguageAnalyzerContext(root, TimeProvider.System); var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken); var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken); var analysis = JavaServiceProviderScanner.Scan(classPath, JavaSpiCatalog.Default, cancellationToken); var service = Assert.Single(analysis.Services, record => record.ServiceId == "java.sql.Driver"); var selected = Assert.Single(service.Candidates.Where(candidate => candidate.IsSelected)); Assert.Equal("com.example.AppDriver", selected.ProviderClass); Assert.Contains(service.Candidates.Select(candidate => candidate.ProviderClass), provider => provider == "com.example.LibDriver"); } finally { TestPaths.SafeDelete(root); } } private static void CreateJarWithClasses( string rootDirectory, string relativePath, IEnumerable classNames, IDictionary serviceDefinitions) { ArgumentNullException.ThrowIfNull(rootDirectory); ArgumentException.ThrowIfNullOrEmpty(relativePath); var jarPath = Path.Combine(rootDirectory, relativePath.Replace('/', Path.DirectorySeparatorChar)); Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!); using var fileStream = new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None); using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: false); var timestamp = new DateTimeOffset(2024, 01, 01, 0, 0, 0, TimeSpan.Zero); foreach (var className in classNames) { var entryPath = className.Replace('.', '/') + ".class"; var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression); entry.LastWriteTime = timestamp; using var writer = new BinaryWriter(entry.Open(), Encoding.UTF8, leaveOpen: false); writer.Write(new byte[] { 0xCA, 0xFE, 0xBA, 0xBE }); } foreach (var pair in serviceDefinitions) { var entryPath = "META-INF/services/" + pair.Key; var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression); entry.LastWriteTime = timestamp; using var writer = new StreamWriter(entry.Open(), Encoding.UTF8, leaveOpen: false); foreach (var provider in pair.Value) { writer.WriteLine(provider); } } } }