feat(ruby): Implement RubyManifestParser for parsing gem groups and dependencies
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
feat(ruby): Add RubyVendorArtifactCollector to collect vendor artifacts test(deno): Add golden tests for Deno analyzer with various fixtures test(deno): Create Deno module and package files for testing test(deno): Implement Deno lock and import map for dependency management test(deno): Add FFI and worker scripts for Deno testing feat(ruby): Set up Ruby workspace with Gemfile and dependencies feat(ruby): Add expected output for Ruby workspace tests feat(signals): Introduce CallgraphManifest model for signal processing
This commit is contained in:
@@ -62,8 +62,8 @@ public sealed class BundleInspectorTests
|
||||
var scan = DenoBundleScanner.Scan(temp, CancellationToken.None);
|
||||
var observations = DenoBundleScanner.ToObservations(scan);
|
||||
|
||||
Assert.Equal(1, scan.EszipBundles.Length);
|
||||
Assert.Equal(1, scan.CompiledBundles.Length);
|
||||
Assert.Single(scan.EszipBundles);
|
||||
Assert.Single(scan.CompiledBundles);
|
||||
Assert.Equal(2, observations.Length);
|
||||
Assert.Contains(observations, obs => obs.BundleType == "eszip");
|
||||
Assert.Contains(observations, obs => obs.BundleType == "deno-compile");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
@@ -21,9 +22,24 @@ public sealed class ContainerAdapterTests
|
||||
var inputs = DenoContainerAdapter.CollectInputs(workspace, observations);
|
||||
|
||||
Assert.NotEmpty(inputs);
|
||||
Assert.Contains(inputs, input => input.Kind == DenoContainerSourceKind.Cache);
|
||||
Assert.Contains(inputs, input => input.Kind == DenoContainerSourceKind.Vendor);
|
||||
Assert.Contains(inputs, input => input.Kind == DenoContainerSourceKind.Bundle);
|
||||
|
||||
var cacheInputs = inputs.Where(input => input.Kind == DenoContainerSourceKind.Cache).ToArray();
|
||||
Assert.Equal(3, cacheInputs.Length);
|
||||
Assert.All(cacheInputs, cache => Assert.True(cache.Metadata.ContainsKey("path")));
|
||||
|
||||
var vendorInputs = inputs.Where(input => input.Kind == DenoContainerSourceKind.Vendor).ToArray();
|
||||
Assert.Equal(2, vendorInputs.Length);
|
||||
Assert.Contains(vendorInputs, vendor => vendor.Metadata.TryGetValue("alias", out var alias) && alias?.Contains("vendor", StringComparison.OrdinalIgnoreCase) == true);
|
||||
|
||||
var bundleInputs = inputs.Where(input => input.Kind == DenoContainerSourceKind.Bundle).ToArray();
|
||||
Assert.Equal(2, bundleInputs.Length);
|
||||
Assert.Contains(bundleInputs, bundle => string.Equals(bundle.Bundle?.BundleType, "eszip", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains(bundleInputs, bundle => string.Equals(bundle.Bundle?.BundleType, "deno-compile", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.All(bundleInputs, bundle =>
|
||||
{
|
||||
Assert.True(bundle.Metadata.ContainsKey("entrypoint"));
|
||||
Assert.True(bundle.Metadata.ContainsKey("moduleCount"));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -70,6 +70,7 @@ public sealed class DenoWorkspaceNormalizerTests
|
||||
|
||||
var graph = DenoModuleGraphResolver.Resolve(workspace, CancellationToken.None);
|
||||
var compatibility = DenoNpmCompatibilityAdapter.Analyze(workspace, graph, CancellationToken.None);
|
||||
var cacheSummary = string.Join(";", workspace.CacheLocations.Select(cache => $"{cache.Kind}:{cache.AbsolutePath}"));
|
||||
|
||||
Assert.NotEmpty(graph.Nodes);
|
||||
Assert.NotEmpty(graph.Edges);
|
||||
@@ -108,7 +109,7 @@ public sealed class DenoWorkspaceNormalizerTests
|
||||
usage => usage.Specifier == "node:fs");
|
||||
|
||||
var npmResolution = compatibility.NpmResolutions.First(res => res.Specifier == "npm:dayjs@1");
|
||||
Assert.True(npmResolution.ExistsOnDisk);
|
||||
Assert.True(npmResolution.ExistsOnDisk, $"ResolvedPath={npmResolution.ResolvedPath ?? "(null)"}");
|
||||
Assert.Equal("deno", npmResolution.Condition);
|
||||
Assert.True(npmResolution.ResolvedPath?.EndsWith("deno.mod.ts", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using StellaOps.Scanner.Analyzers.Lang;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Deno;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Golden;
|
||||
|
||||
public sealed class DenoAnalyzerGoldenTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AnalyzerMatchesGoldenSnapshotAsync()
|
||||
{
|
||||
var fixture = TestPaths.ResolveFixture("lang", "deno", "full");
|
||||
var golden = Path.Combine(fixture, "expected.json");
|
||||
var analyzers = new ILanguageAnalyzer[] { new DenoLanguageAnalyzer() };
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
|
||||
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixture, analyzers, cancellationToken).ConfigureAwait(false);
|
||||
var normalized = Normalize(json, fixture);
|
||||
var expected = await File.ReadAllTextAsync(golden, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
normalized = normalized.TrimEnd();
|
||||
expected = expected.TrimEnd();
|
||||
|
||||
if (!string.Equals(expected, normalized, StringComparison.Ordinal))
|
||||
{
|
||||
var actualPath = golden + ".actual";
|
||||
await File.WriteAllTextAsync(actualPath, normalized, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Assert.Equal(expected, normalized);
|
||||
}
|
||||
|
||||
private static string Normalize(string json, string workspaceRoot)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var normalizedRoot = workspaceRoot.Replace("\\", "/", StringComparison.Ordinal);
|
||||
var builder = json.Replace(normalizedRoot, "<workspace>", StringComparison.Ordinal);
|
||||
var altRoot = workspaceRoot.Replace("/", "\\", StringComparison.Ordinal);
|
||||
builder = builder.Replace(altRoot, "<workspace>", StringComparison.Ordinal);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Scanner.Analyzers.Lang;
|
||||
@@ -37,6 +39,27 @@ public sealed class DenoLanguageAnalyzerObservationTests
|
||||
Assert.True(payload.Content.Length > 0);
|
||||
Assert.NotNull(payload.Metadata);
|
||||
Assert.True(payload.Metadata!.ContainsKey("deno.observation.hash"));
|
||||
|
||||
using var document = JsonDocument.Parse(payload.Content.Span);
|
||||
var root = document.RootElement;
|
||||
|
||||
var entrypoints = root.GetProperty("entrypoints").EnumerateArray().Select(element => element.GetString()).ToArray();
|
||||
Assert.Contains("src/main.ts", entrypoints);
|
||||
|
||||
var capabilities = root.GetProperty("capabilities").EnumerateArray().ToArray();
|
||||
Assert.Contains(capabilities, capability => capability.GetProperty("reason").GetString() == "builtin.deno.ffi");
|
||||
Assert.Contains(capabilities, capability => capability.GetProperty("reason").GetString() == "builtin.node.worker_threads");
|
||||
Assert.Contains(capabilities, capability => capability.GetProperty("reason").GetString() == "builtin.node.fs");
|
||||
|
||||
var dynamicImports = root.GetProperty("dynamicImports").EnumerateArray().Select(element => element.GetProperty("specifier").GetString()).ToArray();
|
||||
Assert.Contains("https://cdn.example.com/dynamic/mod.ts", dynamicImports);
|
||||
|
||||
var literalFetches = root.GetProperty("literalFetches").EnumerateArray().Select(element => element.GetProperty("url").GetString()).ToArray();
|
||||
Assert.Contains("https://api.example.com/data.json", literalFetches);
|
||||
|
||||
var bundles = root.GetProperty("bundles").EnumerateArray().ToArray();
|
||||
Assert.Contains(bundles, bundle => bundle.GetProperty("type").GetString() == "eszip");
|
||||
Assert.Contains(bundles, bundle => bundle.GetProperty("type").GetString() == "deno-compile");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Fixtures;
|
||||
|
||||
internal static class BundleFixtureBuilder
|
||||
{
|
||||
@@ -8,6 +10,10 @@ internal static class BundleFixtureBuilder
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var bundlePath = Path.Combine(directory, "sample.eszip");
|
||||
if (File.Exists(bundlePath))
|
||||
{
|
||||
File.Delete(bundlePath);
|
||||
}
|
||||
|
||||
using (var archive = ZipFile.Open(bundlePath, ZipArchiveMode.Create))
|
||||
{
|
||||
@@ -52,6 +58,10 @@ internal static class BundleFixtureBuilder
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var binaryPath = Path.Combine(directory, "sample.deno");
|
||||
if (File.Exists(binaryPath))
|
||||
{
|
||||
File.Delete(binaryPath);
|
||||
}
|
||||
var eszipPath = CreateSampleEszip(directory);
|
||||
var eszipBytes = File.ReadAllBytes(eszipPath);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.Scanner.Analyzers.Lang.Deno.Fixtures;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
|
||||
@@ -19,106 +20,7 @@ internal static class DenoWorkspaceTestFixture
|
||||
private static void CreateDenoFixture(string root, out string envDenoDir)
|
||||
{
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
File.WriteAllText(Path.Combine(root, "deno.jsonc"), """
|
||||
// sample deno config
|
||||
{
|
||||
"importMap": "./import_map.json",
|
||||
"lock": {
|
||||
"enabled": true,
|
||||
"path": "./deno.lock"
|
||||
},
|
||||
"vendor": {
|
||||
"enabled": true,
|
||||
"path": "./vendor"
|
||||
},
|
||||
"nodeModulesDir": false,
|
||||
"imports": {
|
||||
"$std/": "https://deno.land/std@0.207.0/",
|
||||
"app/": "./src/app/",
|
||||
"data": "./data/data.json",
|
||||
"npmDynamic": "npm:dayjs@1",
|
||||
"nodeFs": "node:fs"
|
||||
},
|
||||
"scopes": {
|
||||
"https://deno.land/": {
|
||||
"fmt/": "https://deno.land/std@0.207.0/fmt/"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Directory.CreateDirectory(Path.Combine(root, "src", "app"));
|
||||
Directory.CreateDirectory(Path.Combine(root, "data"));
|
||||
File.WriteAllText(Path.Combine(root, "data", "data.json"), "{ \"ok\": true }");
|
||||
File.WriteAllText(
|
||||
Path.Combine(root, "import_map.json"),
|
||||
"""
|
||||
{
|
||||
"imports": {
|
||||
"app/": "./src/app/main.ts",
|
||||
"vendor/": "https://deno.land/std@0.207.0/"
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
File.WriteAllText(
|
||||
Path.Combine(root, "deno.lock"),
|
||||
"""
|
||||
{
|
||||
"version": "4",
|
||||
"remote": {
|
||||
"https://deno.land/std@0.207.0/http/server.ts": "sha256-deadbeef",
|
||||
"https://example.com/mod.ts": "sha256-feedface",
|
||||
"node:fs": "builtin"
|
||||
},
|
||||
"redirects": {
|
||||
"https://deno.land/std/http/server.ts": "https://deno.land/std@0.207.0/http/server.ts"
|
||||
},
|
||||
"npm": {
|
||||
"specifiers": {
|
||||
"npm:dayjs@1": "dayjs@1.11.12"
|
||||
},
|
||||
"packages": {
|
||||
"dayjs@1.11.12": {
|
||||
"integrity": "sha512-sample",
|
||||
"dependencies": {
|
||||
"tslib": "tslib@2.6.3"
|
||||
}
|
||||
},
|
||||
"tslib@2.6.3": {
|
||||
"integrity": "sha512-tslib",
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var vendorRoot = Path.Combine(root, "vendor", "https", "deno.land", "std@0.207.0", "http");
|
||||
Directory.CreateDirectory(vendorRoot);
|
||||
File.WriteAllText(Path.Combine(vendorRoot, "server.ts"), "export const vendor = true;");
|
||||
|
||||
var vendorBase = Path.Combine(root, "vendor");
|
||||
File.WriteAllText(
|
||||
Path.Combine(vendorBase, "import_map.json"),
|
||||
"""
|
||||
{
|
||||
"imports": {
|
||||
"std/http/server.ts": "https://deno.land/std@0.207.0/http/server.ts"
|
||||
}
|
||||
}
|
||||
""");
|
||||
File.WriteAllText(
|
||||
Path.Combine(vendorBase, "deno.lock"),
|
||||
"""
|
||||
{
|
||||
"version": "1",
|
||||
"remote": {}
|
||||
}
|
||||
""");
|
||||
|
||||
CreateDenoDir(Path.Combine(root, ".deno"), "workspace.ts", includeRegistry: true);
|
||||
CopyTemplateWorkspace(root);
|
||||
|
||||
envDenoDir = Path.Combine(root, "env-deno");
|
||||
CreateDenoDir(envDenoDir, "env.ts");
|
||||
@@ -130,6 +32,39 @@ internal static class DenoWorkspaceTestFixture
|
||||
var layerVendor = Path.Combine(layerFs, "vendor", "https", "layer.example");
|
||||
Directory.CreateDirectory(layerVendor);
|
||||
File.WriteAllText(Path.Combine(layerFs, "vendor", "import_map.json"), "{\"imports\":{\"layer/\": \"https://layer.example/\"}}");
|
||||
|
||||
var bundlesRoot = Path.Combine(root, "bundles");
|
||||
Directory.CreateDirectory(bundlesRoot);
|
||||
BundleFixtureBuilder.CreateSampleEszip(bundlesRoot);
|
||||
BundleFixtureBuilder.CreateSampleCompiledBinary(bundlesRoot);
|
||||
}
|
||||
|
||||
private static void CopyTemplateWorkspace(string destination)
|
||||
{
|
||||
var template = TestPaths.ResolveFixture("lang", "deno", "full");
|
||||
CopyDirectory(template, destination);
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string source, string destination)
|
||||
{
|
||||
foreach (var directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var relative = Path.GetRelativePath(source, directory);
|
||||
Directory.CreateDirectory(Path.Combine(destination, relative));
|
||||
}
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (file.EndsWith(".actual", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relative = Path.GetRelativePath(source, file);
|
||||
var target = Path.Combine(destination, relative);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
|
||||
File.Copy(file, target, overwrite: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateDenoDir(string root, string fileName, bool includeRegistry = false)
|
||||
@@ -142,7 +77,7 @@ internal static class DenoWorkspaceTestFixture
|
||||
Directory.CreateDirectory(npm);
|
||||
|
||||
File.WriteAllText(Path.Combine(deps, fileName), "export const cache = true;");
|
||||
File.WriteAllText(Path.Combine(gen, $"{Path.GetFileNameWithoutExtension(fileName)}.js"), "console.log('gen');");
|
||||
File.WriteAllText(Path.Combine(gen, $"{Path.GetFileNameWithoutExtension(fileName)}.js"), "console.log(gen);");
|
||||
File.WriteAllText(Path.Combine(npm, "package.json"), "{}");
|
||||
|
||||
if (includeRegistry)
|
||||
|
||||
Reference in New Issue
Block a user