Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

274 lines
9.2 KiB
C#

// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using OpenSandbox.Models;
using Xunit;
namespace OpenSandbox.E2ETests;
[Collection("CSharp E2E Tests")]
public class SandboxManagerE2ETests : IClassFixture<SandboxManagerE2ETestFixture>
{
private readonly SandboxManagerE2ETestFixture _fixture;
public SandboxManagerE2ETests(SandboxManagerE2ETestFixture fixture)
{
_fixture = fixture;
}
[Fact(Timeout = 10 * 60 * 1000)]
public async Task ListSandboxInfos_StatesFilter_IsOrLogic()
{
var manager = _fixture.Manager;
var tag = _fixture.Tag;
var s1 = _fixture.S1;
var s2 = _fixture.S2;
var s3 = _fixture.S3;
var result = await manager.ListSandboxInfosAsync(new SandboxFilter
{
States = new[] { SandboxStates.Running, SandboxStates.Paused },
Metadata = new Dictionary<string, string> { ["tag"] = tag },
PageSize = 50
});
var ids = result.Items.Select(info => info.Id).ToHashSet();
Assert.Contains(s1.Id, ids);
Assert.Contains(s2.Id, ids);
Assert.Contains(s3.Id, ids);
var pausedOnly = await manager.ListSandboxInfosAsync(new SandboxFilter
{
States = new[] { SandboxStates.Paused },
Metadata = new Dictionary<string, string> { ["tag"] = tag },
PageSize = 50
});
var pausedIds = pausedOnly.Items.Select(info => info.Id).ToHashSet();
Assert.Contains(s3.Id, pausedIds);
Assert.DoesNotContain(s1.Id, pausedIds);
Assert.DoesNotContain(s2.Id, pausedIds);
var runningOnly = await manager.ListSandboxInfosAsync(new SandboxFilter
{
States = new[] { SandboxStates.Running },
Metadata = new Dictionary<string, string> { ["tag"] = tag },
PageSize = 50
});
var runningIds = runningOnly.Items.Select(info => info.Id).ToHashSet();
Assert.Contains(s1.Id, runningIds);
Assert.Contains(s2.Id, runningIds);
Assert.DoesNotContain(s3.Id, runningIds);
}
[Fact(Timeout = 10 * 60 * 1000)]
public async Task ListSandboxInfos_MetadataFilter_IsAndLogic()
{
var manager = _fixture.Manager;
var tag = _fixture.Tag;
var s1 = _fixture.S1;
var s2 = _fixture.S2;
var s3 = _fixture.S3;
var tagAndTeam = await manager.ListSandboxInfosAsync(new SandboxFilter
{
Metadata = new Dictionary<string, string> { ["tag"] = tag, ["team"] = "t1" },
PageSize = 50
});
var tagAndTeamIds = tagAndTeam.Items.Select(info => info.Id).ToHashSet();
Assert.Contains(s1.Id, tagAndTeamIds);
Assert.Contains(s2.Id, tagAndTeamIds);
Assert.DoesNotContain(s3.Id, tagAndTeamIds);
var tagTeamEnv = await manager.ListSandboxInfosAsync(new SandboxFilter
{
Metadata = new Dictionary<string, string>
{
["tag"] = tag,
["team"] = "t1",
["env"] = "prod"
},
PageSize = 50
});
var tagTeamEnvIds = tagTeamEnv.Items.Select(info => info.Id).ToHashSet();
Assert.Contains(s1.Id, tagTeamEnvIds);
Assert.DoesNotContain(s2.Id, tagTeamEnvIds);
Assert.DoesNotContain(s3.Id, tagTeamEnvIds);
var tagEnv = await manager.ListSandboxInfosAsync(new SandboxFilter
{
Metadata = new Dictionary<string, string>
{
["tag"] = tag,
["env"] = "prod"
},
PageSize = 50
});
var tagEnvIds = tagEnv.Items.Select(info => info.Id).ToHashSet();
Assert.Contains(s1.Id, tagEnvIds);
Assert.Contains(s3.Id, tagEnvIds);
Assert.DoesNotContain(s2.Id, tagEnvIds);
var noneMatch = await manager.ListSandboxInfosAsync(new SandboxFilter
{
Metadata = new Dictionary<string, string>
{
["tag"] = tag,
["team"] = "t2"
},
PageSize = 50
});
var createdIds = new HashSet<string> { s1.Id, s2.Id, s3.Id };
Assert.DoesNotContain(noneMatch.Items, info => createdIds.Contains(info.Id));
var patched = await manager.PatchSandboxMetadataAsync(s2.Id, new Dictionary<string, string?>
{
["env"] = "stage",
["team"] = null
});
Assert.Equal("stage", patched.Metadata!["env"]);
Assert.False(patched.Metadata!.ContainsKey("team"));
var refreshed = await manager.GetSandboxInfoAsync(s2.Id);
Assert.Equal("stage", refreshed.Metadata!["env"]);
Assert.False(refreshed.Metadata!.ContainsKey("team"));
}
[Fact(Timeout = 2 * 60 * 1000)]
public async Task Manager_InvalidSandboxOperations_ShouldFail()
{
var manager = _fixture.Manager;
var fakeId = $"sandbox-not-exist-{Guid.NewGuid():N}";
await Assert.ThrowsAnyAsync<Exception>(() => manager.GetSandboxInfoAsync(fakeId));
await Assert.ThrowsAnyAsync<Exception>(() => manager.PauseSandboxAsync(fakeId));
await Assert.ThrowsAnyAsync<Exception>(() => manager.ResumeSandboxAsync(fakeId));
await Assert.ThrowsAnyAsync<Exception>(() => manager.KillSandboxAsync(fakeId));
await Assert.ThrowsAnyAsync<Exception>(() => manager.RenewSandboxAsync(fakeId, 300));
}
}
public sealed class SandboxManagerE2ETestFixture : IAsyncLifetime
{
private readonly E2ETestFixture _baseFixture = new();
private SandboxManager? _manager;
private Sandbox? _s1;
private Sandbox? _s2;
private Sandbox? _s3;
private string? _tag;
public SandboxManager Manager => _manager ?? throw new InvalidOperationException("Manager is not initialized.");
public Sandbox S1 => _s1 ?? throw new InvalidOperationException("S1 is not initialized.");
public Sandbox S2 => _s2 ?? throw new InvalidOperationException("S2 is not initialized.");
public Sandbox S3 => _s3 ?? throw new InvalidOperationException("S3 is not initialized.");
public string Tag => _tag ?? throw new InvalidOperationException("Tag is not initialized.");
public async Task InitializeAsync()
{
_manager = SandboxManager.Create(new SandboxManagerOptions
{
ConnectionConfig = _baseFixture.ConnectionConfig
});
_tag = $"csharp-manager-{Guid.NewGuid():N}"[..20];
_s1 = await CreateSandboxAsync(new Dictionary<string, string>
{
["tag"] = _tag,
["team"] = "t1",
["env"] = "prod"
});
_s2 = await CreateSandboxAsync(new Dictionary<string, string>
{
["tag"] = _tag,
["team"] = "t1",
["env"] = "dev"
});
_s3 = await CreateSandboxAsync(new Dictionary<string, string>
{
["tag"] = _tag,
["env"] = "prod"
});
await _manager.PauseSandboxAsync(_s3.Id);
await WaitForStateAsync(_s3.Id, SandboxStates.Paused, TimeSpan.FromMinutes(3));
}
public async Task DisposeAsync()
{
foreach (var sandbox in new[] { _s1, _s2, _s3 })
{
if (sandbox == null)
{
continue;
}
try
{
await sandbox.KillAsync();
}
catch
{
}
await sandbox.DisposeAsync();
}
if (_manager != null)
{
await _manager.DisposeAsync();
}
}
private async Task<Sandbox> CreateSandboxAsync(IReadOnlyDictionary<string, string> metadata)
{
return await Sandbox.CreateAsync(new SandboxCreateOptions
{
ConnectionConfig = _baseFixture.ConnectionConfig,
Image = _baseFixture.DefaultImage,
TimeoutSeconds = _baseFixture.DefaultTimeoutSeconds,
ReadyTimeoutSeconds = _baseFixture.DefaultReadyTimeoutSeconds,
Metadata = metadata,
Env = new Dictionary<string, string> { ["E2E_TEST"] = "true", ["EXECD_API_GRACE_SHUTDOWN"] = "3s", ["EXECD_JUPYTER_IDLE_POLL_INTERVAL"] = "200ms" },
HealthCheckPollingInterval = 500
});
}
private async Task WaitForStateAsync(string sandboxId, string expectedState, TimeSpan timeout)
{
var deadline = DateTime.UtcNow + timeout;
while (true)
{
var info = await Manager.GetSandboxInfoAsync(sandboxId);
if (info.Status.State == expectedState)
{
return;
}
if (DateTime.UtcNow > deadline)
{
throw new TimeoutException($"Timed out waiting for state={expectedState}, last_state={info.Status.State}");
}
await Task.Delay(1000);
}
}
}