ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TiXL 新建项目构建失败的消息分诊设计:从手动测试规范看错误分类、友好提示与 Bug 上报机制

TiXL 新建项目构建失败的消息分诊设计:从手动测试规范看错误分类、友好提示与 Bug 上报机制 TiXL 新建项目构建失败的消息分诊设计从手动测试规范看错误分类、友好提示与 Bug 上报机制【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3本文围绕仓库中的手动测试规范 .tests-manual/build-failure-messages.md 展开结合 NewProjectDialog.cs、Compiler.cs、ProjectSetup.cs 等源码实现系统拆解 TiXL 在「新建项目」这一首次用户必经路径上的构建失败消息设计错误如何被分类已知原因 / 未知原因、如何给出可操作的修复提示、如何触发 Bug 上报以及文件系统异常与配置缺失时的防御回退。读完本文你将掌握这套错误分诊机制的完整测试用例、源码级判定逻辑以及如何在真实环境下复现与验证每一个失败分支。为什么「新建项目失败路径」是绝对不能搞错的地方TiXL 是一个用于创建实时动态图形realtime motion graphics的开源软件用户安装后的第一个动作往往就是File → New Project…创建项目。如果这一步就出现构建失败弹出的对话框是用户对产品的第一印象——因此测试规范开篇就强调These paths are catastrophic to get wrong — a brand-new user whose first action is create project sees only this dialog, so the message has to be intelligible without context.这些路径一旦出错就是灾难性的——全新用户的第一动作就是「创建项目」他只会看到这一个对话框所以消息必须在没有任何上下文的情况下也能看懂。文档 front-matter 给出元数据id: build-failure-messages、added-in-version: 4.3、scope: project-creation、tags: [dev, edge, essential]并把测试场景定位为「dev / 边界情况 / 必备」三个级别的组合。这是一份手动测试manual test规范不是自动化测试脚本因此它严格按照「Action / Expected / Cleanup」三段式编写并要求测试者具备以下前置条件TiXL 处于关闭状态——多个步骤需要修改 PATH 或在启动编辑器前移动文件管理员权限——部分步骤会临时重命名系统文件dotnet.exe一个可安全删除的 scratch 项目目录——在Settings → Project Directories下配置测试后可整体删除。测试的统一入口是Editor菜单 →File→New Project…填写 name namespace 后点击Create。所有场景共享同一条硬性验收底线TiXL 绝不能崩溃也绝不能把原始堆栈直接甩给用户。基线验证Happy Path 与对话框的表单校验测试步骤与验收Action正常启动 TiXLPATH 不变、.NET SDK 已安装打开 New Project 对话框填写唯一项目名如TestHappyPath命名空间留空点击Create。Expected对话框正常关闭不出现任何错误消息框新项目文件夹出现在配置的项目目录下且包含.csproj编辑器 Console 窗口输出Project TestHappyPath created successfully!。源码佐证对话框由 NewProjectDialog.cs 实现它继承自ModalDialog通过Draw()每帧绘制。在点击Create之前表单会做多级校验命名空间校验GraphUtils.IsNamespaceValid(_newSubNamespace, true, out _)不通过时提示Namespace must be a valid and unique C# namespace项目名校验必须是合法 C# 标识符否则提示Name must be a valid C# identifier.、不能包含点Name must not contain dots.、不能为空、不能与已有项目重名DoesProjectWithNameExists遍历所有EditorSymbolPackage的AssemblyInformation.Name末段忽略大小写比较全名拼接fullName ${_userName}.{subNameSpaceWithSeparator}{_newProjectName}预览文本用UiColors.TextMuted合法或UiColors.StatusError非法着色Share Resources 选项默认勾选取消时会以警告色提示「日后无法在不编辑项目代码的情况下修改该选项」。只有allValid为真时Create按钮才可点击。成功路径调用if (ProjectSetup.TryCreateProject(fullName, _shareResources, out var project, out var failureLog)) { T3Ui.Save(false); ImGui.CloseCurrentPopup(); Log.Debug($Project \{project.DisplayName}\created successfully! It can be opened from the project list.); }对应文档验收中的 Console 输出源码实际输出带后续说明文字。注意源码当前把成功消息放在Log.Debug与文档描述的 Console 可见性一致。对话框底部还有一行 hint 文本用于回退场景下的路径可见性验证见后文「空 ProjectDirectories」一节Creates a new project. Projects are used to group operators and resources. You can find your project in {projectFolder}.其中projectFolder Path.Combine(primaryProjectDirectory, _newProjectName)。双分支架构把构建失败「分诊」为已知原因与未知原因这是整套机制的核心。ProjectSetup.TryCreateProject一旦失败会通过out string? failureLog返回失败日志对话框拿到日志后调用Compiler.ExplainBuildFailure(failureLog)做模式识别见 NewProjectDialog.csvar explanation Compiler.ExplainBuildFailure(failureLog); string message; string caption; if (explanation ! null) { // Known cause如缺少 .NET targeting pack——给出可执行的下一步 // 而不是通用的「去提 Bug」。 caption Could not create new project; message $ Failed to create project {_newProjectName} in {_newSubNamespace}. {explanation} The empty project folder has been left on disk; you may want to delete it before retrying. ; BlockingWindow.Instance.ShowMessageBox(message, caption, Ok); } else { // Unknown cause——通用失败分支引导用户上报。 caption Failed to create new project; message $ Failed to create project {_newProjectName} in {_newSubNamespace}. This should never happen - please file a bug report. ... ; var result BlockingWindow.Instance.ShowMessageBox(message, caption, buttons: Copy error and go to report page); ... }两个分支的行为差异非常明确也直接对应测试规范中的验收点维度已知原因分支known cause未知原因分支unknown cause对话框标题Could not create new projectFailed to create new project按钮仅OkCopy error and go to report page及含环境变量的长变体消息内容具体修复提示 空文件夹清理提醒「这不应该发生请提交 Bug 报告」是否上报否是复制日志到剪贴板并打开 GitHub Issues 页面ExplainBuildFailure的判定逻辑在 Compiler.cs识别到已知模式返回人类可读提示否则返回null落到通用分支。目前只有两类已知模式被识别下面两节逐一讲解。场景一DOTNET_NOT_FOUND —— .NET SDK 缺失的友好提示测试步骤与验收Action关闭 TiXL在管理员命令提示符中临时重命名dotnetshim使其在 PATH 上不再可解析ren C:\Program Files\dotnet\dotnet.exe dotnet.exe.disabled或者从 PATH 环境变量中移除dotnet目录并从一个不含它的新 shell 启动 TiXL。启动 TiXL打开 New Project填写唯一项目名点击Create。ExpectedTiXL不崩溃弹出标题为Could not create new project的对话框正文同时包含三要素dotnet命令在 PATH 中未找到指向https://dotnet.microsoft.com/download的链接/URL若刚安装完 dotnet提示重新登录 / 重启以刷新 PATH对话框只有Ok按钮没有Copy error and go to report page——因为这是 known cause 分支不是 unknown-failure 分支。Cleanup测试后恢复 shimren C:\Program Files\dotnet\dotnet.exe.disabled dotnet.exe源码佐证这个分支的判定链值得完整追踪。TiXL 通过dotnetCLI 编译项目在 Compiler.cs 的RunCommand中启动外部进程try { process.Start(); } catch (System.ComponentModel.Win32Exception e) { // Emit a marker ExplainBuildFailure recognises so the caller can show a useful hint. var marker e.NativeErrorCode 2 ? DOTNET_NOT_FOUND : WIN32_EXEC_FAILED; return (${marker}: {fileName}: {e.Message}, -1); }当进程无法启动且NativeErrorCode 2Windows 上对应「找不到指定文件」时会合成一个DOTNET_NOT_FOUND:前缀的标记行其他启动失败则标记为WIN32_EXEC_FAILED。ExplainBuildFailure随后识别该前缀if (buildOutput.StartsWith(DOTNET_NOT_FOUND:, StringComparison.Ordinal)) { return TiXL needs the .NET SDK to compile your projects, but the dotnet command was not found in PATH.\n\n Fix: install the .NET SDK from https://dotnet.microsoft.com/download. If you just installed it, sign out and back in (or reboot) so PATH refreshes — the installer doesnt push the change to already-running processes.; }逐条对应文档验收未找到 dotnetwas not found in PATH、下载链接https://dotnet.microsoft.com/download、刚安装后需签出/重启sign out and back in (or reboot)——三者全部命中且没有任何「上报 Bug」的按钮。顺带一提RunCommand还会为子进程注入三个环境变量这些细节在排查编译环境问题时很有用MSBUILDDISABLENODEREUSE1禁用 MSBuild 节点复用避免跨编译复用陈旧节点DOTNET_CLI_TELEMETRY_OPTOUT1关闭 .NET CLI 遥测DOTNET_MULTILEVEL_LOOKUP0关闭多级查找防止意外解析到非预期位置的 SDK。此外编译设有3 分钟超时process.WaitForExit(3*60*1000)超时后process.Kill(true)强杀进程树并CancelOutputRead/CancelErrorRead防止管道阻塞。场景二NU1100 —— targeting pack 缺失的友好提示测试步骤与验收Action将 .NET SDK 恢复到 PATH 后重启 TiXL。打开 New Project填写唯一项目名如TestNu1100在点击 Create 之前编辑磁盘上正在使用的.csproj模板文档给出的路径是repo/Resources/default-home/或你环境中的等价路径把TargetFramework改成你确定没有安装的 TFM例如net99.0-windows。点击Create。ExpectedTiXL不崩溃弹出标题为Could not create new project的对话框正文同时包含三要素NuGet could not resolve ... for target framework net99.0-windows.NET SDK / targeting pack for net99.0-windows未安装指向https://dotnet.microsoft.com/download的链接/URL磁盘上存在空的TestNu1100文件夹——对话框会提示用户重试前可能想删除它。Cleanup恢复.csproj模板删除半创建的TestNu1100文件夹。源码佐证ExplainBuildFailure用一个编译期正则识别 NuGet 的 NU1100 错误码Compiler.csprivate static readonly Regex _nu1100Regex new( NU1100:\s*Unable to resolve ([^]) for ([^]), RegexOptions.Compiled | RegexOptions.IgnoreCase);匹配成功后把捕获到的包名pkg与目标框架tfm动态填入提示模板var nu1100 _nu1100Regex.Match(buildOutput); if (nu1100.Success) { var pkg nu1100.Groups[1].Value; var tfm nu1100.Groups[2].Value; return $NuGet could not resolve {pkg} for target framework {tfm}.\n\n $This usually means the .NET SDK / targeting pack for {tfm} $is not installed on this machine.\n\n $Fix: install the matching .NET SDK from https://dotnet.microsoft.com/download $(make sure it includes the targeting pack for {tfm}), or update the projects $TargetFramework to a TFM you do have installed.; }由此生成的正文与文档验收逐字对应。注意这里与 DOTNET_NOT_FOUND 一样走的是known cause 分支——只有Ok按钮不上报 Bug。从编译链路看NU1100 主要发生在dotnet restore阶段。TryCompileCompiler.cs将 restore 与 build 分离先执行dotnet restore project --nologo -p:NuGetAuditfalseNuGetAuditfalse是为了避免 restore 时下载审计数据库、在慢网络下拖慢数十秒且其警告用户也看不到restore 失败即返回失败日志成功后再执行dotnet build ... --no-restore --no-dependenciesDebug 构建跳过引用图遍历直接针对编辑器已加载的依赖 DLL 编译。restore 输出的错误会连同 build 输出一起进入failureLog最终被ExplainBuildFailure识别。关于「模板路径」的说明文档中的模板编辑路径为repo/Resources/default-home/或等价路径。实际项目中.csproj模板的具体落盘位置与安装/克隆布局相关测试时以你环境中实际存放项目模板的位置为准——从源码结构看最终创建文件的工作由CsProjectFile.CreateNewProject完成见 ProjectSetup.cs模板文件是它读取的输入。场景三通用构建失败 —— 回落到 Bug 上报分支测试步骤与验收Action恢复模板后创建新项目如TestGenericFail。创建成功后打开其主.cs源文件并引入一个语法错误例如删除一个右大括号。在编辑器中保存以触发重编译。ExpectedTiXL不崩溃弹出报告构建失败的对话框对话框包含Copy error and go to report page按钮以及含环境变量的长变体——这是 unknown-cause 分支用户被引导提交 Bug点击该按钮会把失败日志复制到剪贴板并在默认浏览器打开 GitHub Issues 页面。Cleanup恢复源文件删除TestGenericFail文件夹。源码佐证语法错误不属于ExplainBuildFailure能识别的两类模式因此explanation为null落入未知分支。此时消息框提供两个按钮见 NewProjectDialog.csconst string button Copy error and go to report page; const string buttonWithEnvironmentVariables Copy error environment variables and go to report page\n (Most helpful, but check your list of variables before submitting to avoid leaking sensitive information); var result BlockingWindow.Instance.ShowMessageBox(message, caption, buttons: button); var hasResult !string.IsNullOrWhiteSpace(result); ReportError(report: hasResult, includeEnvVars: result buttonWithEnvironmentVariables || !hasResult, failureLog, fullName, primaryProjectDirectory);ReportError负责组装上报内容同一文件的局部函数第 189–226 行Failure Log完整构建输出可选追加环境变量includeEnvVars为真时把Environment.GetEnvironmentVariables()拼接进## Environment variables:段落取环境变量失败时会替换为异常消息Project detailsTiXL 用户名、项目命名空间、项目名、项目全名、项目目录Additional details留空的注释占位符!--Insert any relevant additional details here, if any--。随后把组装好的文本SetClipboardText写入剪贴板并用OpenWithDefaultApplication打开 GitHub Issues 页面。长按钮文案特意警告用户「提交前检查变量列表避免泄露敏感信息」——这与长变体按钮的命名直接呼应。场景四文件系统失败 —— project files 创建失败的异常路由测试步骤与验收Action关闭 TiXL。把项目目录配置为只读或不存在的路径——例如将Settings → Project Directories[0]指向一个已移除的 USB 驱动器上的路径或指向C:\Windows\ReadOnlyTest\普通用户通常无写权限。启动 TiXL打开 New Project填写TestReadOnly点击Create。ExpectedTiXL不崩溃弹出标题为Failed to create new project的对话框正文包含Failed to create project files on disk:后跟底层 OS 错误对话框包含Copy error and go to report page按钮通用失败分支——文件系统错误目前还没有 known-cause 提示。Cleanup恢复项目目录。源码佐证文件系统失败走的是另一条通道不是「编译失败」而是创建文件本身抛异常。ProjectSetup.TryCreateProject用 try/catch 包住模板落盘操作ProjectSetup.cs// Filesystem failures (OneDrive virtualisation, broken symlinks, antivirus, // missing/removed drives) surface as exceptions; route them through failureLog. CsProjectFile newCsProj; try { newCsProj CsProjectFile.CreateNewProject(name, nameSpace, shareResources, UserSettings.Config.ProjectDirectories[0]); } catch (Exception e) { failureLog $Failed to create project files on disk: {e.Message}; Log.Error(failureLog); newProject null; return false; }源码注释直接列举了现实世界中会命中此分支的典型故障OneDrive 虚拟化、损坏的符号链接、杀毒软件拦截、驱动器缺失/被移除。该失败日志以Failed to create project files on disk:开头但ExplainBuildFailure并不识别它所以落到通用失败分支——标题为Failed to create new project并提供 Bug 上报按钮与文档验收一致。也就是说TryCreateProject的所有失败文件系统异常、编译失败、release info 缺失、home guid 为空最终都统一通过failureLog汇入同一个分诊入口只是不同失败类型被识别为 known/unknown 的结局不同。场景五空 ProjectDirectories —— 防御性回退测试步骤与验收Action关闭 TiXL。编辑userSettings.json位于%APPDATA%\TiXLversion\把ProjectDirectories设为空数组[]。启动 TiXL——启动时应自动用默认文件夹填充它。打开 New Project不点击 Create观察对话框底部提示文本Creates a new project. ... You can find your project in ...。ExpectedTiXL 正常启动提示文本显示Documents\TiXLversion\下的有效路径默认回退值而不是缺失路径也不崩溃点击Create成功。Cleanup无需清理——TiXL 已在启动时持久化了默认文件夹。源码佐证此场景防的是「用户设置文件损坏导致 ProjectDirectories 为空」的边界情况。对话框在 NewProjectDialog.cs 做了显式保护// ProjectDirectories is normally populated at startup but can be empty // on a corrupted user-settings file. Drawn every frame, so guard the indexer. var projectDirectories UserSettings.Config.ProjectDirectories; var primaryProjectDirectory projectDirectories.Count 0 ? projectDirectories[0] : FileLocations.DefaultProjectFolder;由于Draw()每帧执行若直接索引[0]空列表会抛异常导致崩溃——因此用Count 0守卫并回退到默认目录。默认目录定义在 FileLocations.cspublic static readonly string DefaultProjectFolder Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), VersionedAppFolderName);其中VersionedAppFolderName形如TiXL4.3preview 构建为TiXL4.3-alpha也可通过环境变量TIXL_OVERRIDE_VERSION_ID或启动参数--override-version-id覆盖与文档中的Documents\TiXLversion\一致。设置目录同理SettingsDirectory %APPDATA%\TiXLversionFileLocations.cs正是文档中编辑userSettings.json的位置。此外 UserSettings.cs 在加载配置时会执行RemoveDuplicateProjectDirectories()去重避免ProjectDirectories出现重复项影响[0]语义。文档中的 hint 文本You can find your project in ...正是上一节「Happy Path」部分展示的FormInputs.AddHint输出其projectFolder使用的是上面计算出的primaryProjectDirectory——空列表时自然显示为默认回退路径。这套机制的价值延伸同一分诊器也在服务启动加载值得注意的一点是Compiler.ExplainBuildFailure并不只服务于「新建项目」。在 ProjectSetup.Startup.cs 的LoadProjects中编辑器启动加载既有项目时若重编译失败同样会调用它if (needsCompile !csProjFile.TryRecompile(true, out var failureLog)) { Log.Error($Failed to recompile project {csProjFile.Name}:\n{failureLog}); var explanation Compiler.ExplainBuildFailure(failureLog); if (explanation ! null) Log.Warning($Likely cause for {csProjFile.Name} compile failure:\n{explanation}); return new ProjectLoadInfo(fileInfo, csProjFile, false, The project failed to compile., explanation ?? See the log for details.); }也就是说同一套「已知原因 → 可读提示 / 未知原因 → 详情记录」的分诊逻辑被复用于项目创建与项目加载两条路径。从源码结构可以推断这正是测试规范把它标记为essential的原因一套稳定的错误语义同时降低了新建用户与老用户在升级后遇到 SDK 环境问题时的新手门槛。测试要点总表测试场景触发方式对话框标题按钮关键验收信息Happy Path正常环境创建无错误框—Console 输出created successfully!.csproj落盘DOTNET_NOT_FOUND重命名/移除dotnetCould not create new project仅Ok未找到 dotnet、下载链接、重启提示NU1100模板 TFM 改为未安装版本Could not create new project仅OkNuGet 无法解析 TFM、targeting pack 提示、下载链接、空文件夹清理提醒通用构建失败源文件引入语法错误Failed to create new projectCopy error and go to report page剪贴板失败日志、浏览器打开 Issues 页文件系统失败只读/不存在项目目录Failed to create new projectCopy error and go to report pageFailed to create project files on disk: OS 错误空 ProjectDirectoriesuserSettings.json置空数组正常启动Create可用hint 显示Documents\TiXLversion\回退路径所有场景共享三条铁律不崩溃、消息无需上下文即可读懂、known-cause 分支绝不把用户推向 Bug 上报。这套「分类 → 提示 → 上报」的错误分诊架构加上 NewProjectDialog.cs、Compiler.cs 中可复现的判定逻辑与 ProjectSetup.cs 的异常路由构成了 TiXL 新建项目体验的最后一道、也是最关键的一道防线。【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进