Merge commit '0952958af7685646cdbf2a4907c6e2ae59d4d352'
This commit is contained in:
@@ -1,37 +1,39 @@
|
||||
using Flyshot.Core.Config;
|
||||
using Flyshot.Core.Domain;
|
||||
|
||||
namespace Flyshot.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 锁定 Task 3 的兼容输入行为,确保旧配置、.robot 元数据和路径策略都能被稳定加载。
|
||||
/// 锁定 Task 3 的兼容输入行为,确保旧配置、JSON 模型元数据和路径策略都能被稳定加载。
|
||||
/// </summary>
|
||||
public sealed class ConfigCompatibilityTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证现有 RobotConfig.json 能被加载,并保持关键机器人参数与飞拍程序内容不变。
|
||||
/// 验证现有旧样本配置能被加载,并保持关键机器人参数与飞拍程序内容不变。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotConfigLoader_LoadsLegacyRobotConfig_AndPreservesPrograms()
|
||||
{
|
||||
var workspaceRoot = GetWorkspaceRoot();
|
||||
var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "RobotConfig.json");
|
||||
var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "EOL10_EAU_0.json");
|
||||
|
||||
var loaded = new RobotConfigLoader().Load(configPath);
|
||||
|
||||
Assert.True(loaded.Robot.UseDo);
|
||||
Assert.Equal([7, 8], loaded.Robot.IoAddresses);
|
||||
Assert.Equal(2, loaded.Robot.IoKeepCycles);
|
||||
Assert.Equal(7, loaded.Robot.TriggerSampleIndexOffsetCycles);
|
||||
Assert.Equal(1.0, loaded.Robot.AccLimitScale);
|
||||
Assert.Equal(1.0, loaded.Robot.JerkLimitScale);
|
||||
Assert.Equal(1.0, loaded.Robot.PlanningSpeedScale);
|
||||
Assert.True(loaded.Robot.SmoothStartStopTiming);
|
||||
Assert.Equal(5, loaded.Robot.AdaptIcspTryNum);
|
||||
|
||||
var program = Assert.Contains("001", loaded.Programs);
|
||||
Assert.Equal("001", program.Name);
|
||||
Assert.Equal(5, program.Waypoints.Count);
|
||||
Assert.Equal(3, program.ShotWaypointCount);
|
||||
var program = Assert.Contains("EOL10_EAU_0", loaded.Programs);
|
||||
Assert.Equal("EOL10_EAU_0", program.Name);
|
||||
Assert.Equal(45, program.Waypoints.Count);
|
||||
Assert.Equal(42, program.ShotWaypointCount);
|
||||
Assert.Empty(program.AddressGroups[0].Addresses);
|
||||
Assert.Equal([8, 7], program.AddressGroups[1].Addresses);
|
||||
Assert.Equal([4, 3], program.AddressGroups[1].Addresses);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -69,8 +71,12 @@ public sealed class ConfigCompatibilityTests
|
||||
Assert.False(loaded.Robot.UseDo);
|
||||
Assert.Empty(loaded.Robot.IoAddresses);
|
||||
Assert.Equal(3, loaded.Robot.IoKeepCycles);
|
||||
Assert.Equal(7, loaded.Robot.TriggerSampleIndexOffsetCycles);
|
||||
Assert.Equal(0.5, loaded.Robot.AccLimitScale);
|
||||
Assert.Equal(0.25, loaded.Robot.JerkLimitScale);
|
||||
Assert.Equal(1.0, loaded.Robot.PlanningSpeedScale);
|
||||
Assert.Equal(1.0, loaded.Robot.SpeedRatio);
|
||||
Assert.True(loaded.Robot.SmoothStartStopTiming);
|
||||
Assert.Equal([0, 0, 0], program.OffsetValues);
|
||||
Assert.All(program.AddressGroups, group => Assert.Empty(group.Addresses));
|
||||
}
|
||||
@@ -81,13 +87,173 @@ public sealed class ConfigCompatibilityTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 .robot 解析会保留 Joint3 对 Joint2 的 couple 元数据,并构造规划侧可直接消费的 RobotProfile。
|
||||
/// 验证 RobotConfig.json 可以显式配置运行时默认速度倍率,供 MoveJoint 和后续执行链路初始化使用。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotConfigLoader_LoadsRuntimeSpeedRatio()
|
||||
{
|
||||
var tempRoot = CreateTempDirectory();
|
||||
try
|
||||
{
|
||||
var configPath = Path.Combine(tempRoot, "legacy.json");
|
||||
File.WriteAllText(
|
||||
configPath,
|
||||
"""
|
||||
{
|
||||
"robot": {
|
||||
"use_do": true,
|
||||
"io_keep_cycles": 2,
|
||||
"acc_limit": 1.0,
|
||||
"jerk_limit": 1.0,
|
||||
"speed_ratio": 0.65
|
||||
},
|
||||
"flying_shots": {
|
||||
"demo": {
|
||||
"traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
|
||||
"shot_flags": [false, false, false, false]
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loaded = new RobotConfigLoader().Load(configPath);
|
||||
|
||||
Assert.Equal(0.65, loaded.Robot.SpeedRatio, precision: 6);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 RobotConfig.json 可以显式配置规划限速倍率,且该倍率独立于运行时 J519 速度倍率。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotConfigLoader_LoadsPlanningSpeedScale()
|
||||
{
|
||||
var tempRoot = CreateTempDirectory();
|
||||
try
|
||||
{
|
||||
var configPath = Path.Combine(tempRoot, "legacy.json");
|
||||
File.WriteAllText(
|
||||
configPath,
|
||||
"""
|
||||
{
|
||||
"robot": {
|
||||
"use_do": true,
|
||||
"io_keep_cycles": 2,
|
||||
"acc_limit": 1.0,
|
||||
"jerk_limit": 1.0,
|
||||
"planning_speed_scale": 0.742277
|
||||
},
|
||||
"flying_shots": {
|
||||
"demo": {
|
||||
"traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
|
||||
"shot_flags": [false, false, false, false]
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loaded = new RobotConfigLoader().Load(configPath);
|
||||
|
||||
Assert.Equal(0.742277, loaded.Robot.PlanningSpeedScale, precision: 6);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 RobotConfig.json 可以显式配置触发绑定后的命令 sample 后移周期数。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotConfigLoader_LoadsTriggerSampleIndexOffsetCycles()
|
||||
{
|
||||
var tempRoot = CreateTempDirectory();
|
||||
try
|
||||
{
|
||||
var configPath = Path.Combine(tempRoot, "legacy.json");
|
||||
File.WriteAllText(
|
||||
configPath,
|
||||
"""
|
||||
{
|
||||
"robot": {
|
||||
"use_do": true,
|
||||
"io_keep_cycles": 2,
|
||||
"trigger_sample_index_offset_cycles": 8,
|
||||
"acc_limit": 1.0,
|
||||
"jerk_limit": 1.0
|
||||
},
|
||||
"flying_shots": {
|
||||
"demo": {
|
||||
"traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
|
||||
"shot_flags": [false, false, false, false]
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loaded = new RobotConfigLoader().Load(configPath);
|
||||
|
||||
Assert.Equal(8, loaded.Robot.TriggerSampleIndexOffsetCycles);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 RobotConfig.json 可以关闭飞拍执行前的二次平滑起停时间重映射。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotConfigLoader_LoadsSmoothStartStopTimingSwitch()
|
||||
{
|
||||
var tempRoot = CreateTempDirectory();
|
||||
try
|
||||
{
|
||||
var configPath = Path.Combine(tempRoot, "legacy.json");
|
||||
File.WriteAllText(
|
||||
configPath,
|
||||
"""
|
||||
{
|
||||
"robot": {
|
||||
"use_do": true,
|
||||
"io_keep_cycles": 2,
|
||||
"acc_limit": 1.0,
|
||||
"jerk_limit": 1.0,
|
||||
"smooth_start_stop_timing": false
|
||||
},
|
||||
"flying_shots": {
|
||||
"demo": {
|
||||
"traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
|
||||
"shot_flags": [false, false, false, false]
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loaded = new RobotConfigLoader().Load(configPath);
|
||||
|
||||
Assert.False(loaded.Robot.SmoothStartStopTiming);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 JSON 模型解析会保留 Joint3 对 Joint2 的 couple 元数据,并构造规划侧可直接消费的 RobotProfile。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotModelLoader_LoadsRobotProfile_WithJointLimitsAndCoupling()
|
||||
{
|
||||
var workspaceRoot = GetWorkspaceRoot();
|
||||
var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot");
|
||||
var replacementRoot = GetReplacementRoot();
|
||||
var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json");
|
||||
|
||||
var profile = new RobotModelLoader().LoadProfile(modelPath);
|
||||
|
||||
@@ -105,13 +271,13 @@ public sealed class ConfigCompatibilityTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到模型关节限制上。
|
||||
/// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到 JSON 模型关节限制上。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotModelLoader_AppliesAccelerationAndJerkScales()
|
||||
{
|
||||
var workspaceRoot = GetWorkspaceRoot();
|
||||
var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot");
|
||||
var replacementRoot = GetReplacementRoot();
|
||||
var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json");
|
||||
|
||||
var profile = new RobotModelLoader().LoadProfile(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25);
|
||||
|
||||
@@ -120,10 +286,57 @@ public sealed class ConfigCompatibilityTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证路径兼容层既能补旧目录候选,也能按平台策略生成默认用户数据目录。
|
||||
/// 验证 JSON 模型可一次解析后同时生成规划约束视图和运动学几何视图。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PathCompatibility_ResolvesLegacyCandidates_AndBuildsUserDataRoots()
|
||||
public void RobotModelLoader_LoadsProfileAndKinematics_FromSingleParse()
|
||||
{
|
||||
var replacementRoot = GetReplacementRoot();
|
||||
var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json");
|
||||
|
||||
var loaded = new RobotModelLoader().LoadProfileAndKinematics(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25);
|
||||
|
||||
Assert.Equal("FANUC_LR_Mate_200iD_7L", loaded.Profile.Name);
|
||||
Assert.Equal(modelPath, loaded.Profile.ModelPath);
|
||||
Assert.Equal(6, loaded.Profile.DegreesOfFreedom);
|
||||
Assert.Equal(14.905, loaded.Profile.JointLimits[2].AccelerationLimit, precision: 3);
|
||||
Assert.Equal(62.115, loaded.Profile.JointLimits[2].JerkLimit, precision: 3);
|
||||
|
||||
Assert.Equal("FANUC_LR_Mate_200iD_7L", loaded.KinematicsModel.Name);
|
||||
Assert.True(loaded.KinematicsModel.Joints.Count >= loaded.Profile.DegreesOfFreedom);
|
||||
Assert.Contains(loaded.KinematicsModel.Joints, static joint => joint.Name == "Joint3" && joint.CoupleMaster == "Joint2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证路径兼容层只从当前服务配置目录解析相对配置,并按平台策略生成默认用户数据目录。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PathCompatibility_ResolvesConfigDirectoryOnly_AndBuildsUserDataRoots()
|
||||
{
|
||||
var tempRoot = CreateTempDirectory();
|
||||
try
|
||||
{
|
||||
var configPath = Path.Combine(tempRoot, "Config", "sample.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(configPath)!);
|
||||
File.WriteAllText(configPath, "{}");
|
||||
|
||||
var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot);
|
||||
|
||||
Assert.Equal(configPath, resolved);
|
||||
Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix));
|
||||
Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证旧父工作区候选路径存在时也不会被相对配置解析隐式命中。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PathCompatibility_DoesNotResolveLegacyWorkspaceFallbacks()
|
||||
{
|
||||
var tempRoot = CreateTempDirectory();
|
||||
try
|
||||
@@ -132,11 +345,9 @@ public sealed class ConfigCompatibilityTests
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(legacyConfigPath)!);
|
||||
File.WriteAllText(legacyConfigPath, "{}");
|
||||
|
||||
var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot);
|
||||
var exception = Assert.Throws<FileNotFoundException>(() => PathCompatibility.ResolveConfigPath("sample.json", tempRoot));
|
||||
|
||||
Assert.Equal(legacyConfigPath, resolved);
|
||||
Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix));
|
||||
Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows));
|
||||
Assert.Equal("sample.json", exception.FileName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -144,6 +355,19 @@ public sealed class ConfigCompatibilityTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证默认加载配置时使用当前 replacement 仓库内的 Config/RobotConfig.json。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RobotConfigLoader_LoadsRepositoryConfigFromReplacementConfigDirectory()
|
||||
{
|
||||
var replacementRoot = GetReplacementRoot();
|
||||
|
||||
var loaded = new RobotConfigLoader().Load("RobotConfig.json");
|
||||
|
||||
Assert.Equal(Path.Combine(replacementRoot, "Config", "RobotConfig.json"), loaded.SourcePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定位当前工作区根目录,便于复用父仓库中的真实样本。
|
||||
/// </summary>
|
||||
@@ -164,6 +388,25 @@ public sealed class ConfigCompatibilityTests
|
||||
throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定位 replacement 仓库根目录,供测试读取仓库内固化配置。
|
||||
/// </summary>
|
||||
private static string GetReplacementRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln")))
|
||||
{
|
||||
return current.FullName;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Unable to locate the flyshot replacement root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建当前测试专用的临时目录,避免不同测试之间相互污染。
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user