148 lines
6.2 KiB
C#
148 lines
6.2 KiB
C#
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<string, string[]>
|
|
{
|
|
["java.sql.Driver"] = new[] { "com.example.ADriver" },
|
|
};
|
|
|
|
var servicesB = new Dictionary<string, string[]>
|
|
{
|
|
["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<string, string[]>
|
|
{
|
|
["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<string> classNames,
|
|
IDictionary<string, string[]> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|