diff --git a/.agents/docs/architecture.md b/.agents/docs/architecture.md index c213cf5..9e8174a 100644 --- a/.agents/docs/architecture.md +++ b/.agents/docs/architecture.md @@ -1,6 +1,6 @@ # mcpp-vscode 架构 -> 状态:0.4.0(mcppls 语言服务迁移之后)。本文记录**当前**结构;变动请同步更新。 +> 第 1–6 节保留 0.4.0 迁移背景;Libraries 的当前架构与边界见第 7 节(2026-10-18)。 ## 1. 一句话定位 @@ -75,3 +75,29 @@ extension.ts VS Code 装配层:激活、命令注册、provide (build / restart)不符。 具体建议见 `.agents/reviews/` 下的评审报告。 + + +## 7. Libraries(2026-10-18) + +- `library/indexLocator.ts`:显式索引路径或 CLI home 下的官方索引发现;新位置 `indices/mcpplibs` 优先,兼容 `registry/data/mcpplibs`。不默认收集 xim。缓存按源路径、平台、描述符 stat 和更新标记区分;项目状态是单独的 overlay。 +- `library/projectState.ts`:复用 TOML 解析器,区分普通、开发与构建声明、源码依赖、锁定结果和有歧义的短名;生成清单预览的目标 section/key。 +- `library/libraryView.ts` 与 `library/detailPanel.ts`:VS Code 生命周期和 CLI 编排;搜索响应绑定请求代次,添加操作绑定包、项目、页面代次,详情读取还单独校验渲染请求顺序。 +- `library/libraryHtml.ts`、`library/detailHtml.ts`:纯渲染器。初始文档只安装一次;mounted 仅请求重放最新数据,后续用 message 更新局部 DOM,保留查询、版本、依赖类型、折叠状态与焦点,不形成 reload loop。 +- `library/projectWatcher.ts`:只比较选中项目的 manifest/lock 内容;详情实际读到的内容更新比较基线,保证短暂 lock 状态也能恢复。监听和计时器随窗口或项目切换释放。 +- `library/addDependency.ts`:普通和开发依赖由 `mcpp add` 修改,构建依赖/分组迁移只提供复制片段与打开清单。扩展不自行改写 TOML,也不将 lock-only 误称为已声明/已安装。 + +本地真实 UI 验证入口:`tools/test-libraries-ui.mjs`;专用 profile 和产物均在 gitignored 的 `.dev-profile/`。 + +- `library/examples.ts`:官方安装版索引未携带测试时,读取带来源提交的内置测试快照;checkout 的本地测试优先,自定义索引不使用此兜底。完整代码通过只读 `mcpp-example` 文档打开。 +- `projects/dependencyGraph.ts`:纯声明图遍历,默认三跳(公共画布可请求更深数据),处理路径包、共享节点和循环;lock 只补版本。`dependencyGraphPanel.ts` 负责只读文件编排和本地 Mermaid Webview。Mermaid 固定版本与许可证由编译脚本复制至 `dist/vendor`,无需 CDN。 + +- `views/models.ts` 与 Libraries 共用直接声明解析,树节点携带项目、依赖和分组身份;点击进入声明图并居中高亮对应节点。Webview 使用 SVG 平移/缩放变换,滚轮保留鼠标锚点,Pointer Events 区分点击与拖动;Fit 可恢复完整图。 + +## 8. Shared dependency canvas and C++ Modules graph + +- `graphs/model.ts` defines a provider-independent graph, component roots, depth/branch projection, 150-node/300-edge layout budgets and escaped Mermaid output. Complete module data is retained independently of the visible subgraph. +- `projects/dependencyGraphPanel.ts` owns one persistent Webview with separate per-project/per-kind graph and navigation states. `graphs/panelHtml.ts` and `media/graph.js` share rendering, wheel/pointer navigation, folding, focus, search and theme handling. Layouts are serialized; obsolete provider requests are discarded. +- `cli/moduleGraph.ts` is a trust-gated, time/output-bounded consumer of mcppls CLI JSON. This extends the integration boundary with structured queries, without taking over C++ parsing, resolution, LSP ownership or mcppls settings. Prefer the installed dependency extension's payload, with PATH fallback when absent. +- `projects/moduleGraph.ts` validates the result, reads manifest entry conventions and determines file ownership by the nearest project manifest. Preserve all internal edges and just the first hop into another package; `external=false` alone cannot identify project ownership. Generated sources outside the project boundary are conservatively treated as external. +- `webview/readOnlyDocuments.ts` is the shared content-addressed virtual-document store used by examples and graph navigation; graph messages contain node identities, and the host opens only provider files attached to those identities. +- Basics contains C++ Modules Deps; declared package Dependencies follow as a collapsed sibling before Common commands. Existing language-server module-graph forwarding commands remain compatible. diff --git a/.agents/docs/mcpp-integration.md b/.agents/docs/mcpp-integration.md index 0aa53bb..2ed9c00 100644 --- a/.agents/docs/mcpp-integration.md +++ b/.agents/docs/mcpp-integration.md @@ -7,6 +7,11 @@ | 调用点 | 命令 | 依赖的输出 | | --- | --- | --- | +| `library/indexLocator` | `mcpp self env --format json` | `mcpp.env` 信封中的 `mcppHome`,按 CLI/项目上下文缓存 | +| `library/detailPanel` | `mcpp xpkg parse --json` | 裸 JSON;按需解析单个描述符,失败时标明文本回退 | +| `library/libraryView` | `mcpp search ` | 尽力解析人类文本,仅产生有限预览 | +| `library/addDependency` | `mcpp add @ [--dev]` | 退出码;完成后重读 manifest/lock | +| `extension` | `mcpp index update` | 退出码;与本地只读重载区分 | | `cliController.newProject` | `mcpp new ` | 退出码 | | `tasks.projectTaskPlan` | `mcpp build` / `run` / `test` / `clean` | 退出码(VS Code Task) | | `cliController.readToolchainInventory` | `mcpp toolchain list` | **人类文本**(正则解析) | @@ -15,7 +20,7 @@ `mcpp.path` 为空时用 `"mcpp"`,由 VS Code 进程的 `PATH` 解析。 -## 2. mcpp 已提供的稳定机读接口(本扩展未使用) +## 2. mcpp 已提供的稳定机读接口 mcpp 有正式的机读输出协议(`docs/50-machine-output.md`)。**检测规则**:解析 stdout, 要求 `schemaVersion` 与 `kind` 存在;**不要**用退出码或"命令没报错"来判断支持与否。 @@ -78,3 +83,11 @@ mcpp 有正式的机读输出协议(`docs/50-machine-output.md`)。**检测 3. `mcpp new ` 的模板不做 TOML/C++ 转义,且名字包含 `PROJECT` 时模板替换不终止 (mcpp#380);`newProject.ts` 因此做名称白名单校验。 4. `--configure-only` 会写 `compile_commands.json`,成功条件是退出码 0 且该文件可解析。 + + +## Libraries 的 CLI 边界(本机 mcpp 2026.10.16.3 验证) + +- 官方索引位于 `/indices/mcpplibs`;`registry/data/xim-pkgindex` 是工具索引,不能作为官方库缺失时的替代。旧版官方路径 `registry/data/mcpplibs` 仅作兼容。 +- `MCPP_HOME` 优先;CLI 还可能按可执行文件位置确定自包含 home,扩展应使用 `self env` 的答案,不能合并多个安装的同名索引。 +- `mcpp add --help` 只提供 `--dev`。没有 `--build`,也不承诺在普通与开发分组之间迁移声明;扩展对这两类清单编辑提供 TOML 预览与打开文件入口。 +- 当前环境平台来自 extension host,不能称为工程交叉编译目标或完整 ABI 兼容性结论。 diff --git a/.agents/docs/mcppls-integration.md b/.agents/docs/mcppls-integration.md index d3a0695..20348d1 100644 --- a/.agents/docs/mcppls-integration.md +++ b/.agents/docs/mcppls-integration.md @@ -178,3 +178,13 @@ mcppls 侧发生以下任一变化时,本文件与 `src/languageServer.ts` 必 - 平台矩阵变化; - 出现新的公开刷新/状态命令; - 服务器 `executeCommandProvider` 列表变化(影响 §3 的命名约束)。 + +## 结构化模块图查询(2026-10-19) + +项目的共享依赖画布另通过依赖扩展随附的 mcppls CLI 调用 +`query module --graph --root --timeout 20 --format json`。这与原有 +`mcppls.showModuleGraph` UI 转发入口并存,不解析那个命令生成的 Markdown。 +`modules{name,files,external}` / `imports{from,to}` / `snapshot` / `truncated` +在消费边界校验,非零退出或不兼容结果可刷新重试。仅受信任工作区调用,宿主 +30 秒超时、8 MiB 输出限制;全图接口自身当前最多返回 500 个模块并标明截断。 +本扩展负责图布局、项目边界过滤和只读文件导航,C++ 语法与语义仍完全归 mcppls。 diff --git a/.agnets/docs/2026-10-18-libraries-ux-optimisation-plan.md b/.agnets/docs/2026-10-18-libraries-ux-optimisation-plan.md new file mode 100644 index 0000000..a51084e --- /dev/null +++ b/.agnets/docs/2026-10-18-libraries-ux-optimisation-plan.md @@ -0,0 +1,333 @@ +# Libraries 与包详情页优化方案 + +日期:2026-10-18 +状态:已实施并完成本地验证,等待 review(验证记录见 [本地验收](2026-10-19-libraries-local-verification.md)) +范围:索引来源、Libraries 浏览与搜索、包详情布局、版本选择、添加依赖及状态同步。 + +本文按本次要求放在 `.agnets/docs`;仓库已有文档目录实际为 `.agents/docs`,本次不迁移旧文档。 + +## 1. 结论与设计方向 + +Libraries 应围绕一条完整路径设计:**找到 C++ 库 → 判断能否使用 → 查看引入方式 → 为明确的项目添加明确的版本 → 得到准确反馈**。 + +先修数据来源和状态一致性,再优化显示层。延续侧边栏选包、编辑器区看详情的结构;保留单个详情面板、离线优先、简洁搜索工具栏、主操作与仓库/索引站按钮同行的已有决策。 + +建议本轮不加入多行筛选 chips、复杂卡片网格、评分/下载量、自动安装、自动修改版本、任意目标平台选择或在线 README 抓取。 + +## 2. 分析依据与确认程度 + +初稿依据仓库源码、历史评审记录以及本机只读 CLI/文件检查。实施后已在真实 VS Code 窗口验证下文布局、交互与异常恢复;具体环境、步骤和边界见本地验收记录。 + +主要代码: + +| 文件 | 关注点 | +| --- | --- | +| `src/library/indexLocator.ts` | 索引发现、快照缓存、示例读取 | +| `src/library/libraryView.ts` | 列表生命周期、网络搜索、刷新和设置监听 | +| `src/library/libraryHtml.ts` | 列表行、搜索、前端状态 | +| `src/library/detailPanel.ts` | 详情加载、解析、项目上下文、异步操作 | +| `src/library/detailHtml.ts` | 版本矩阵、主按钮、复制、反馈 | +| `src/library/indexModel.ts` | 身份、依赖声明/锁定状态、示例和用法推导 | +| `src/library/addDependency.ts` | `mcpp add`、通知与输出 | +| `media/library.css` | 侧边栏与详情页共享样式 | +| `src/extension.ts` | 项目获取、索引更新及依赖变化回调 | + +已有交互约束参考 `.agents/docs/archive/2026-10-02-ui-ux-optimisation-plan.md` 的 §19、§20、§22:曾明确移除多行筛选标签,并将添加、仓库、索引站操作合并到一行。本方案不直接恢复被移除的结构。 + +### 2.1 已确认的问题 + +| 优先级 | 现状与证据 | 用户影响 | +| --- | --- | --- | +| P0 | 本机 mcpp 2026.10.16.3 的官方索引位于 `~/.mcpp/indices/mcpplibs`,含 252 个 Lua 描述符;插件只默认扫描 `registry/data` | 官方库漏读 | +| P0 | `rootAt()` 仅检查 `pkgs/`;本机旧扫描目录下 `xim-pkgindex` 含 337 个描述符 | Libraries 显示成 xim 包列表 | +| P0 | 快照 key 含索引 revision 和项目路径,不含 manifest/lock 内容;保存和添加后虽调用 refresh,仍可能命中旧快照 | 已添加徽标不更新 | +| P0 | 添加结果经共享 session 发给当前面板,没有绑定发起操作的包、项目或页面代次 | A 包添加期间切到 B,A 的结果可能把 B 标成已添加 | +| P1 | 网络搜索结果只在列表 `extra` 中;详情 `buildModel()` 只查本地 snapshot | 可点击的搜索结果打开后显示“不在本地索引” | +| P1 | 搜索有 debounce,但没有请求代次校验;旧请求可晚于新请求返回,关闭开关后也可能回填 | 搜索结果与当前关键词/开关不一致 | +| P1 | 详情创建后等待完整 `buildModel()` 才 paint,解析允许等待 20 秒 | 首次可能空白,切包期间可能仍显示上一包内容 | +| P1 | 所有平台的版本均为可点击按钮,选中状态只比较版本字符串 | 可误选其它平台独有版本;相同版本跨行同时高亮 | +| P1 | `installed` 合并 manifest 声明与 lock 解析状态;前端只保留版本字符串;dev 初始值未从现有声明恢复 | 已声明、已解析、依赖分组混淆;同版本切换分组无法准确表达 | +| P1 | 详情的项目通过当前编辑器动态获取,没有页面内明确目标;详情切换到活动编辑器本身也可能改变项目推断条件 | 多项目场景下难以判断写入哪个项目 | +| P2 | 详情为最长 900px 的纵向文档,版本、依赖在示例前;全部版本铺开 | 宽窗口利用不足,阅读用法需要滚动 | +| P2 | 复制、链接和添加共用一条结果栏,添加还触发通知;添加前端没有明确 pending 锁定 | 反馈互相覆盖、重复点击缺少解释 | + +补充:当前只根据 extension host 的 `process.platform` 判定平台;这不能等同于工程交叉编译目标。界面应称“当前环境平台”,不得宣称完整验证了工具链、架构和 ABI 兼容性。 + +## 3. 索引来源与数据语义 + +### 3.1 默认源选择 + +1. 非空 `mcpp.library.indexPath` 优先。保留索引根目录、`pkgs/` 目录、索引集合目录三种输入;无效显式路径直接显示路径错误,不静默换源。 +2. 默认发现 `/indices/mcpplibs`,兼容 `/registry/data/mcpplibs` 旧位置;同一 home 新位置有效时不再叠加旧位置。 +3. home 选择遵循 CLI 的实际规则。建议实现时以本地 CLI 源码与 `self env --format json` 确认:显式环境 home 优先,默认 home 作为后备;需要 CLI 探测时每会话缓存,显式刷新/环境配置变更可重试。不把两个 home 中的同名官方源默认合并。 +4. 默认不遍历加载全部 registry;官方源缺失时显示“未找到 mcpp-index”,即使 xim 已存在,也不能用 xim 填满列表。 +5. 默认模式排除 xim;用户显式指定自定义源时允许浏览,但标题/来源须明确为自定义,不能标成官方。 + +“来源身份”与“官网链接”拆开建模:`official / custom / other-registry` 由发现路径和可核实元数据决定;有无 `site` 只决定是否展示官网按钮。集合配置中的同名包必须保留来源,选定优先级前不能静默覆盖。 + +底部简短显示 `mcpp-index · 252 个包 · 本地`,详细信息可展开查看实际路径、索引版本/更新时间(仅有真实元数据时显示)、最近更新结果。计数是本地快照数量,不保证等于远端当前总量。 + +### 3.2 缓存与刷新 + +- 索引内容缓存和项目依赖状态分离。刷新依赖状态只重读当前项目 manifest/lock,不重读数百个描述符。 +- 索引缓存 key 包含源的规范路径、版本标记;显式本地重读可强制失效。源码 checkout 无更新 marker 时,应能检测描述符修改,不能只依赖根目录 mtime 和第一层目录数。 +- 监听 manifest/lock 的保存及磁盘创建、变更、删除;去抖后同时更新列表和当前详情。项目切换、索引源/CLI 配置变化也应更新上下文。 +- 区分两个操作:**重新读取本地索引**与**更新 mcpp 索引**。前者只读磁盘,后者执行 `mcpp index update`,可能联网。 +- 更新失败保留上一次可用数据,标明未更新成功,并提供重试/查看输出;不把异常降级为空列表。 +- 配置的是自定义 checkout 时,本地重读读取该路径;全局 `mcpp index update` 不承诺更新这个 checkout,操作说明必须指出其作用范围。 + +## 4. Libraries 列表交互与布局 + +### 4.1 工具栏与行结构 + +```text +Libraries [更新索引] +[搜索包名、命名空间、描述 ×] +☐ 搜索其它 registry 12 / 252 +───────────────────────────────────────── +compat.argparse 3.2 +#include · 已声明 +Command line argument parser… + +openkal.types 0.x +import +Type definitions… +───────────────────────────────────────── +mcpp-index · 本地 [来源详情] +``` + +上图版本与描述仅为结构示意,产品显示以真实索引为准。 + +- 保留搜索框和一个可选跨 registry 搜索开关,不常驻命名空间/用法筛选标签。 +- 包 ID 为主文本;版本为次文本;用法与项目状态一行,描述最多两行。示例/生态等次要标签移到详情,避免每行出现多枚同等权重徽标。 +- 已声明状态用文字加主题色;当前选中包增加可见标记与 `aria-current`。不在整行按钮内部嵌套“添加”按钮。 +- 支持 Tab/Enter 的基础操作;可进一步加入上下键移动结果、Enter 打开、Esc 清空搜索。键盘行为须与搜索框编辑行为明确区分。 +- 查询仍在客户端即时过滤;保留当前多词匹配,并让精确 ID、名称前缀优先,其后为描述命中;无查询时稳定按 ID 排序。 +- 保存 query、scroll、selectedKey;数据刷新后保留焦点和光标位置。使用 `setState/getState` 恢复被销毁的 webview,不依赖 DOM 长期存活。 + +### 4.2 搜索状态与来源 + +- 本地搜索始终立即可用;其它 registry 默认关闭。 +- 启用后只对至少两个字符的查询发起搜索;清空/短查询及时清理旧附加结果。 +- 每次请求记录 query、请求代次、开关及项目/源上下文;响应只提交到仍匹配的视图。关闭开关立即废弃在途结果。 +- 显示“正在搜索其它 registry…”,不阻塞本地结果。其它来源单独分组,行内注明来源;状态分别显示本地命中数与额外结果数。 +- 首期为非本地结果提供**有限预览详情**:包 ID、搜索返回的版本/描述、来源、可复制的检索命令,以及“如何获取完整详情”的说明。缺失字段不补造;暂不提供一键添加,避免把工具索引结果直接当作 C++ 库添加。 +- 取消当前“能点击但详情必然在本地查不到”的流程;后续有稳定解析接口时再接入跨源完整详情,不以解析人类输出构造完整包模型。 + +### 4.3 空状态 + +| 状态 | 展示与恢复入口 | +| --- | --- | +| 初次读取 | “正在读取 mcpp-index…”;有旧快照时保留列表 | +| 官方索引缺失 | 说明缺失来源;提供更新索引、设置路径 | +| 显式路径无效 | 展示路径和原因;提供修改路径、重试 | +| 搜索无命中 | 展示当前查询;提供清空,以及可选搜索其它 registry | +| 读取/更新失败 | 简短原因、重试、查看输出;保留可用旧结果 | +| 无 mcpp 项目 | 可浏览;顶部提示尚未选择项目,详情主操作解释为何不可添加 | + +## 5. 详情页布局 + +### 5.1 推荐信息顺序 + +1. **身份与概览**:包 ID、描述、用法、标准与许可;来源为次级信息。 +2. **项目与添加控制**:目标项目、当前环境平台、版本选择、依赖类型,以及原有同行的添加/仓库/索引站操作。 +3. **如何使用**:引入代码、真实示例及来源;用户无需越过全部平台版本矩阵。 +4. **版本与平台**:当前平台完整列表,其他平台默认折叠查看。 +5. **依赖与构建信息**:依赖清单、项目锁定对照、targets/include dirs;空白构建信息不单独占一节。 +6. **来源与诊断**:本地路径、解析来源、索引版本、降级信息。 + +### 5.2 布局草图 + +```text +compat.argparse +Command line argument parser… +#include · C++23 · MIT + +目标项目 app-a 当前环境 linux +版本 [3.2 ▾] 依赖类型 [普通 ▾] +项目声明 3.1 · 锁定解析 3.1 +[切换到 3.2] [仓库] [索引站] +mcpp add compat.argparse@3.2 [复制] + +如何使用 包信息 / 平台版本 +引入代码 [复制] 当前平台 linux +真实示例 [复制] 其它平台(折叠) + 依赖(数量) + 构建信息(折叠) +``` + +- 详情容器建议最大宽度约 1120px;可用宽度 ≥900px 时下半部分为主内容约 2/3、补充信息约 1/3。较窄编辑器降为单栏,不依赖窗口总宽度。 +- 添加区保持首屏可见,不在页面底部重复按钮;宽窗口下主操作与外链仍同行,窄窗口自然换行。 +- 暂不引入整页 tabs:当前信息量适合一个文档配局部折叠,避免用法和状态被藏在不同页签。 +- 长包名允许换行,代码区单独横向滚动;布局不产生整页横向滚动。字体、颜色、焦点和分隔线沿用 VS Code theme tokens。 +- 初次打开立刻显示包身份和局部加载状态;先展示本地轻量信息,解析完成只更新对应区块。切包时立即换标题和加载占位,不能让旧包按钮继续可操作。 +- 保留单详情面板;重新点同一包保留版本、依赖类型、折叠项和滚动位置。切包可缓存少量会话状态,索引更新后校验所选版本是否仍存在。 + +## 6. 版本、项目与添加行为 + +### 6.1 版本选择 + +- 主操作区使用单一版本选择器,默认现有声明版本(仍在当前环境可用集合时),否则选择当前环境最新可用版本;显式展示存在更新,不自动升级。 +- “最新”限定为当前环境,不表示跨平台最新。来自文本回退的可用性标记为“待 CLI 确认”;解析失败不能渲染成“确定无版本”。 +- 主选择器只列当前环境可用版本。其他平台矩阵只读;查看其它平台不改变命令、不启用添加。 +- 没有当前环境版本时禁用添加,明确原因;未知平台、解析失败、明确不支持平台分别处理。 +- 当前环境平台依据现有 host 检测;工程目标识别属于后续 CLI 契约扩展,首期不以平台下拉框模拟切换编译目标。 + +### 6.2 项目依赖状态 + +统一列表和详情使用的项目状态模型,分别保留:`declaredVersion`、`resolvedVersion`、`dependencyKind`、`sourceType`。短名匹配必须在无歧义时采用;不同命名空间同名不能误判。 + +| 状态 | 主操作和显示 | +| --- | --- | +| 无项目 | 禁用添加;提供选择/打开项目入口 | +| 多项目 | 展示已选择项目;首次不能明确定位时要求选择一次 | +| 不受信任 | 可读本地内容;禁用运行 CLI 的操作并显示原因 | +| 未声明 | “添加到项目” | +| 同版本同分组已声明 | “已声明”,禁用重复操作 | +| 不同版本已声明 | “切换到 X”,同时展示现有声明和锁定值 | +| 同版本不同分组 | 不误判为已完成;仅在 CLI 确认支持安全迁移时提供“改为开发/普通依赖” | +| path/git 依赖 | 明确显示来源;首期不直接以 registry 版本覆盖,保留查看 manifest 入口 | +| 仅 lock 中存在 | 标记“锁定中”,不能当作项目直接声明或已安装 | + +详情页绑定明确项目上下文;切项目后重新生成项目状态。提交时冻结 `{packageKey, projectRoot, version, dependencyKind, requestId, pageGeneration}`,操作期间即使焦点改变也不能重新选择写入项目。 + +首期继续通过 `mcpp add` 修改依赖。实现前验证其分组迁移、依赖替换、锁文件更新语义;未支持的操作应说明限制,不由扩展自行编辑 TOML 来补齐。 + +### 6.3 操作反馈与并发 + +- 点击添加立即进入 pending,按钮显示“正在添加…”,锁定本页版本/分组选择;host 同样去重。 +- 包/项目/页面代次与响应匹配时才更新当前页面;A 的操作完成时,B 页面不接收 A 的已添加标记。A 的完成状态可写入对应项目状态并保存在操作记录中。 +- 成功后重读 manifest/lock,以真实文件更新列表、详情和按钮;不只把请求中的版本当作最终状态。 +- 复制使用按钮旁短暂“已复制”;外链失败在该操作附近提供复制链接/重试。它们不覆盖添加结果。 +- 添加成功默认使用页内反馈;切走详情后使用通知。失败页内显示短原因与“查看输出/重试”,避免整段 stderr 同时填满通知和页面。 +- 首期不新增伪取消按钮。若底层进程没有可靠取消接口,不能只隐藏 pending 却让命令继续运行。 + +## 7. 用法、示例和依赖信息的真实性 + +- 将真实示例、描述符明确声明的引入方式、推导的参考写法分别标注来源。 +- 没有可验证的模块名/头文件路径时显示说明,不能把 `#include ` 或按包名猜出的 import 当作可直接复制的真实用法。 +- 本机打包索引目前没有 `tests/examples`,因此“暂无本地示例”是正常状态,不暗示包不支持使用。可提供已有的索引站链接;不自动联网补齐。 +- 示例提供代码复制与来源文件信息;只在本地确有文件时提供打开文件操作。完整文件查看按需加载,不一次读完所有示例。 +- 依赖信息要区分包描述符声明与当前项目 lock 对照;未确认所属版本时标为描述符层级信息,不能随着版本选择器变化就声称已切换到该版本依赖。 +- 可解析为本地包的依赖支持打开详情;无法解析的显示文本和来源说明。首期不绘制完整依赖图。 + +## 8. 实施拆分 + +| 阶段 | 交付内容 | 主要文件 | +| --- | --- | --- | +| P0:数据与状态正确 | 新旧官方索引定位;默认排除 xim;源身份;依赖状态缓存拆分;操作绑定包与项目 | `indexLocator.ts`、`indexModel.ts`、`detailPanel.ts`、`addDependency.ts`、`extension.ts` | +| P1:完成核心路径 | 列表来源与空态;搜索请求代次;跨源有限预览;详情加载占位;当前环境版本选择;项目与 dev 状态恢复;添加反馈 | `libraryView.ts`、`libraryHtml.ts`、`detailPanel.ts`、`detailHtml.ts` | +| P2:阅读与可达性 | 响应式主辅栏;用法/示例前移;其他平台折叠;来源诊断收起;选择/滚动恢复;键盘与焦点 | `detailHtml.ts`、`libraryHtml.ts`、`media/library.css` | + +可按 P0、P1、P2 分三个可 review 变更,每阶段可独立验收。调整相关中英文文案、设置说明、架构契约和原有测试;旧测试中“所有版本均为按钮”等断言应随最终行为更新。 + +不要求新增公共配置来控制每个布局细节。优先让默认行为正确,减少新增持久化设置。 + +## 9. 验收清单 + +### 9.1 必须覆盖的行为验证 + +1. 新版官方路径 + xim 并存:默认只显示官方源;以测试夹具计数断言,真实机器 252/337 只作本次证据。 +2. 只有旧版官方路径、只有 xim、显式无效路径、显式 checkout、多 home/重复源分别有明确结果。 +3. manifest/lock 保存、CLI 磁盘写入和项目切换后,列表与详情状态一致;未改变索引不重读全量描述符。 +4. 查询 A 慢、查询 B 快以及关闭网络开关:A 结果均不能覆盖 B/重新出现。 +5. 跨源结果可打开有限详情,不出现伪装成索引删除的错误页。 +6. A 包添加期间切到 B/切换项目/关闭再打开页面:A 的结果不得修改 B 或写入新项目。 +7. 当前平台无版本、其它平台独有版本、同版本跨平台、已有 dev、仅 lock、path/git、同名不同命名空间均正确展示。 +8. 添加失败、解析超时、更新失败均可恢复,已有数据保留;取消/关闭加载不能留下旧操作控件。 + +对索引发现、缓存失效、操作关联和竞态补充行为测试;HTML 测试验证语义与可达性,不以大量脆弱的 DOM 字符串断言替代真实交互。 + +### 9.2 人工视觉与交互验收 + +- VS Code 真实窗口:侧边栏约 180/280/420px;详情约 480/800/1200px;浅色、深色、高对比主题。 +- 首屏可判断目标项目、当前环境、所选版本和主操作;查看用法不必先滚过其它平台全部版本。 +- 键盘可完成搜索 → 打开 → 选版本 → 添加;焦点可见,状态有文本,折叠和选择状态有 aria 表达。 +- 搜索不重建文档、不丢光标;更新/重新打开不丢查询和滚动;添加/复制不引发整页刷新。 +- 用网络延迟/解析超时模拟加载过程,确认不展示可操作的上一包内容。 + +性能目标为本地数百包查询输入流畅(单次过滤目标 <50ms),列表不为每个包启动 CLI;优先测量现状,只有实际规模要求时再引入虚拟列表。 + +## 10. 本次 review 建议确认的决策 + +1. **来源范围**:默认只浏览官方 mcpp-index;自定义路径明确标识,其它 registry 通过可选搜索进入。 +2. **侧边栏密度**:保留搜索 + 一个跨源开关;行内只保留关键用法与项目状态,不恢复多行 chips。 +3. **详情层级**:项目/版本/添加放首屏,用法和示例优先,平台矩阵与构建信息降为补充;宽屏双栏、窄屏单栏。 +4. **版本行为**:默认保留现有可用声明版本;其它平台只读,当前环境版本由一个选择器控制。 +5. **跨源边界**:首期做有限预览,未有完整数据的结果不提供一键添加。 +6. **实施顺序**:先 P0 数据与并发正确性,再 P1 核心交互,最后 P2 视觉与键盘细节。 + + +## 11. 实施期间的 review 调整(优先于前文布局草图) + +- 移除叠加的顶部 margin/padding 和空反馈占位;没有本地示例或依赖时,不为每个空字段单独占用一节。 +- `#include`/`import`、`C++23`、许可证作为使用主题颜色的小 tag,直接放在包名后;项目切换与当前环境位于右上角。 +- 移除 dev checkbox。主添加按钮旁提供依赖类型二级菜单,包含项目依赖(`dependencies`)、开发依赖(`dev-dependencies`)、构建依赖(`build-dependencies`)。版本仍由唯一的当前环境选择器决定。 +- 选普通/开发依赖时,按钮和 CLI 预览同步变化;选择构建依赖时,显示真实命名空间对应的 TOML 片段,主按钮变为“打开 mcpp.toml”。本机 `mcpp add --help` 仅提供 `--dev`,因此构建依赖不伪造 `--build`,也不自动写文件。 +- 已在普通/开发分组声明的包切换到另一个分组时,同样提供清单编辑流程,不通过 CLI 制造重复声明或假定能安全迁移。 +- 平台矩阵、构建信息和来源诊断默认收起。保持文档布局,不引入整页 tabs。 +- 对选中项目同时提供文件事件监听和两个文件的低频内容检查;本地真实测试发现宿主 inotify 配额耗尽时,普通 watcher 会漏掉外部修改。详情实际读到的内容也会记录为比较基线,以发现轮询间隔中创建后删除的 lock 文件。 + +## 12. 第二轮 review:测试示例、单个清除按钮、项目依赖图 + +- 安装版索引缺少 `tests/examples`,但真实源码仓库有测试。这是详情用法一直为空的直接原因。优先读取 checkout 中的测试;官方安装版缺失时使用插件内置、带提交来源的官方测试快照。自定义索引不混用官方快照。 +- 按包自身命名的测试项目优先于偶然依赖它的测试项目;显示真实测试正文,代码区限制高度,可打开完整只读源码。本次快照匹配本机 252 包中的 179 个,其余保持诚实的空态。 +- 搜索输入改用具有 `searchbox` 语义的普通文本框,只提供一个自定义清除按钮;输入为空时隐藏按钮,避免浏览器原生 × 重复显示。 +- “基本信息 → 依赖”右侧加入项目依赖图按钮。根项目为第 0 层,展示最近 3 层依赖;普通、开发、构建与条件声明区分关系,路径包继续读本地清单。 +- lock 文件没有父子边,因此只提供版本;关系来自 manifest 与本地索引描述符,明确标为声明图,不声称是实际构建解析图。缺少本地元数据的节点使用虚线,不把所有 lock 包错误连接到每个直接依赖。 +- Mermaid 随插件打包,离线渲染;支持缩放、刷新、复制 Mermaid 和打开清单。循环/共享节点去重,规模限制为 120 节点、240 条边。 + +## 13. 第三轮 review:代码阅读与依赖导航 + +- 示例代码的逐行白条来自 VS Code 默认内联 `code` 背景,代码块显式重置为透明背景及块级显示。复制与源码入口同“示例代码”标题同行,使用紧凑图标按钮;复制反馈不移除图标。 +- 官方快照和本地 checkout 示例统一使用按内容哈希生成的只读虚拟文档,不再直接打开可编辑的索引文件。 +- Dependencies 仅显示直接声明的 `namespace.package@version`,区分索引、path、git 和开发/构建分组。复用 Libraries 的命名空间解析器,避免把 `[dependencies.compat]` 误当成一个 `compat` 包;不再显示无意义的 `Resolved —`。 +- 每个依赖条目携带项目根目录与依赖身份,点击打开同一个图面板并定位、高亮目标节点。图面板获得焦点后仍保留当前项目上下文。 +- 图首屏只保留标题、项目名和紧凑操作区,移除绝对路径、统计段落和大段解释;图例与源码收起。 +- 连线上完整显示 `namespace.package@version`;节点内显示名称、少量使用方式/许可证或 path 标签及 deps 数量。项目蓝、path 紫、索引绿、缺失元数据橙,保留虚线与文字以避免仅依赖颜色。被定位节点以明显轮廓高亮。 + +- 依赖图支持以鼠标位置为中心的滚轮缩放、按住鼠标拖动画布;保留节点点击高亮,拖动不会误触节点。提供 Fit 复位和 `+` / `-` / `0` 键盘操作。真实 UI 校验缩放锚点、双轴平移及复位。 + +## 14. C++ Modules 图与公共图画布(本轮实施设计) + +### 14.1 数据与范围 + +- 基本信息新增独立的「C++ Modules Deps」入口;同一个依赖图面板通过 Packages / C++ Modules 切换,两类视图分别保存缩放、平移、展开和选中状态。现有 mcppls 图命令保留兼容。 +- 模块关系只消费 `mcppls query module --graph --root --format json` 的结构化结果,不重新扫描或正则解析 C++。全图含 `modules{name,files,external}`、`imports{from,to}`、`snapshot` 和 `truncated`;单模块查询可补充单位角色、分区与导入者。 +- 使用依赖扩展随包携带的 mcppls,缺失时再查 PATH。命令有超时与输出上限,仅在受信任工作区执行;失败显示可重试空态,不回退为自建 C++ 扫描器。 +- 内部归属依据源码对应的项目边界,而非仅依赖 `external`:项目内嵌 path 包、指向外部包的路径均视为外部。本项目内部保留完整关系;从任意内部节点直接导入的外部模块保留一层,外部模块的出边不展示。未使用的标准库节点不混入图。 +- 实现单元的普通 `module M` 归属及声明角色以 mcppls 查询结果为准。全图没有的精确声明行/逐边 export-import 标记不猜测;首次提供准确文件跳转,后续可从服务接口补位置。 + +### 14.2 入口、关系与只读导航 + +- 节点以模块名为主,显示少量角色标签、相对源码文件及 imports 数;普通非模块导入文件也有节点,因此 `main.cpp → app` 可见。 +- manifest 的 `[targets.*].main`、`[lib].path` 优先标记入口;缺省遵守 `src/main.cpp`、`src/<包尾名>.cppm` 规则,仅在真实存在的图文件上标记。多个声明入口保留名称,库入口与模块卡片合一,不制造重复入口节点。 +- import 边方向为使用者 → 提供者;模块卡片的 `export module` 标签表达声明,文件名表达提供位置。项目内部蓝色,分区紫色,外部绿色,未知来源灰色;不以颜色作为唯一依据。 +- 单击节点高亮,并在旁边编辑器打开只读源码,保留画布;多个提供文件先选择。源码通过公共只读虚拟文档服务打开,不能修改原文件;没有源码时保持节点可选并说明原因。拖动绝不触发文件打开。 + +### 14.3 折叠、定位与性能 + +- “本项目内部不限深度”指数据范围完整,首屏默认展开 3 层。提供 3 / 5 / 全部;全部仍受预算限制。外部始终仅一层,不随深度选择扩张。 +- 边界节点显示可操作的隐藏数量,按分支展开下一层/收起。循环和共享节点用稳定身份去重;收起一支不能移除其他可见分支仍需的节点。独立或循环分量也保留入口,不因无入度根而消失。 +- 可见子图在 Mermaid 布局前计算,不能通过 CSS 隐藏节点冒充折叠。每帧最多 150 节点、300 边(含折叠占位);达到预算保留展开提示,允许聚焦该节点,避免强行缩成不可读大图。 +- 搜索覆盖完整内部数据及一层外部节点;选中隐藏结果进入聚焦视图,显示导入者与被导入者,并可返回项目全景。折叠或展开保留视角,Fit 显式复位。 +- 公共层分为 GraphModel、可见子图策略、Mermaid 渲染与 GraphPanel;复用主题、鼠标锚点缩放、拖动、Fit、键盘、高亮、复制与消息校验。两种 provider 负责各自语义,不把模块关系当成包关系。 + +### 14.4 验收 + +- 真实 mcppls JSON 验证 main → 模块、lib.path/默认入口、分区、内部深链及内部任意深度导入外部;path 包即使 external=false 也必须停在外部一层。 +- 验证 3/5/全部、逐支展开/收起、循环/共享节点、独立模块、隐藏节点搜索聚焦、150/300 双预算及 truncated 提示。 +- 在真实 VS Code 验证只读源码、画布保留、拖动不误打开、视图切换状态恢复和原包图回归;用深链、宽扇出和共享密集三类数据测量子图策略与 Mermaid 布局耗时,再决定预算是否需要下调。 + +### 14.5 最新侧栏布局调整(覆盖前文同级关系) + +- Basics 内保留项目身份、工具链、C++ Modules Deps 和语言服务块。 +- Dependencies 从 Basics 移出,作为 Basics 后面的第 2 个顶层分组,默认折叠;Common commands 随后排列。直接依赖条目的坐标、来源与点击定位保持不变。没有直接依赖时继续省略空分组。 +- 初始展开状态为默认值,尊重 VS Code 已保存的用户展开/折叠状态。 + +## 15. 全屏详情自适应修正 + +- 移除详情页 1120px 固定最大宽度与居中规则;内容随编辑器区域铺开,两侧 gutter 为 16–40px,不随宽屏无限增长。 +- 宽屏主栏占据剩余空间,次要信息栏宽度限制在 240–320px;额外宽度优先用于真实示例代码,避免辅助栏按 1/3 比例扩大。 +- 小于 900px 保持单栏,窄屏仍使用 16px 边距,避免分屏出现横向溢出。 +- 验证覆盖实际 Webview 480/800/1200/1600/2560px × 深色/浅色/高对比:同时断言页面无溢出、两侧留白上限、辅助栏宽度和主栏占比,而不只检查 grid 是否存在。 diff --git a/.agnets/docs/2026-10-19-libraries-local-verification.md b/.agnets/docs/2026-10-19-libraries-local-verification.md new file mode 100644 index 0000000..b7b828b --- /dev/null +++ b/.agnets/docs/2026-10-19-libraries-local-verification.md @@ -0,0 +1,130 @@ +# Libraries 本地验收与 review + +日期:2026-10-19(Asia/Tokyo) +分支:`feat/libraries-ux` +方案:[Libraries 与包详情页优化](2026-10-18-libraries-ux-optimisation-plan.md) + +## 核心变化 + +1. 默认浏览官方 mcpp-index。新版路径为 `$MCPP_HOME/indices/mcpplibs`,旧版仅兼容 `registry/data/mcpplibs`;不再扫描所有 registry 并混入 xim。本机官方 252 包、xim 337 包是本次环境的观察值,不作为固定产品数量。 +2. 详情顶部去掉空白占位;包名后紧跟引入方式、C++ 标准和许可证小标签。右上角切换项目并显示环境;版本选择与添加操作集中在首屏。 +3. 用添加按钮旁的二级菜单选择项目、开发、构建依赖,去掉 dev checkbox。选择结果同步按钮、命令预览及声明位置;菜单支持键盘操作。 +4. manifest 声明与 lock 解析版本分别展示。项目切换、磁盘修改、添加结果和异步请求都绑定对应项目与包,避免旧结果污染当前页面。 +5. 详情保留单页阅读。宽屏双栏、窄屏单栏;平台矩阵、构建信息和来源诊断默认收起。无真实示例时不猜造可复制的 include/import。 + +真实本地 review 窗口(中文界面): + +![Libraries 与紧凑详情](assets/libraries-review.png) + +![依赖类型二级菜单](assets/libraries-dependency-menu.png) + +## 本地体验 + +运行: + +```bash +node tools/review-libraries.mjs +# 已经打包时: +node tools/review-libraries.mjs --no-package +``` + +脚本将 VSIX 安装到隔离扩展目录,用隔离 VS Code 用户配置打开 `.dev-profile/library-review`。使用真实 `mcpp` 与官方本地索引,保留后续手动编辑;可通过 `MCPP_DEV_CODE` 指定 VS Code 命令。进入 MC++ → Libraries,打开 `compat.argparse` 或 `compat.zlib`。 + +建议 review: + +- 看包名标签、右上角项目选择和首屏密度;拖窄详情检查换行。 +- 展开添加按钮旁的箭头,比较三种依赖类型及命令/清单预览。 +- 选择 secondary 子项目,添加依赖后核对写入对应 `mcpp.toml`。 +- 切换普通与开发分组,检查已有声明提示;手动修改 manifest/lock 后观察状态更新。 +- 搜索并用方向键、Enter 打开详情;切换包后返回,检查查询、选项及滚动状态。 + +## 测试与证据 + +环境:Linux,VS Code 1.132.0,真实 mcpp 2026.10.16.3。 + +| 验证 | 结果 | +| --- | --- | +| `npm test`,包含配置、翻译、图标、生成文件一致性检查 | 709 通过,0 失败 | +| `npm run test:e2e`,真实 VS Code,语言服务五种能力变体 | 每种 8 项,共 40 通过 | +| `node tools/test-libraries-ui.mjs`,真实 VS Code Webview | 20 项通过 | +| `npm run package` | VSIX 158 文件,约 1.38 MB(含 Mermaid 与官方测试快照) | + +Webview 验证覆盖:官方源及已有声明;lock 新建与删除;索引更新失败保留数据;键盘搜索及文档稳定;真实 `add --dev` 与磁盘核对;添加过程中切包;慢查询过期丢弃及跨源有限预览;项目切换后的写入位置;标签和依赖二级菜单;搜索/解析失败重试;真实 20 秒解析超时恢复;添加失败重试;侧栏宽度及过滤延迟;三种主题下的响应式布局。 + +侧栏按 180/280/420px 验证;详情按 480/800/1200px,在深色、浅色、高对比主题下均无横向溢出。过滤计时与布局测量、完整结果 JSON 由脚本写入 `.dev-profile/libraries-ui-artifacts/`。可选 Playwright 驱动装在仓库外,复现命令见脚本头注释。 + +新增行为测试覆盖新版/旧版/显式路径、默认排除 xim、项目状态覆盖与索引缓存、同名来源与依赖分组、异步操作关联、文件变化及 watcher 释放。测试夹具使用临时目录,不依赖本机包数量。 + +`data/buildscript-api.json` 和 `data/toml-schema.json` 另有独立 checkpoint 更新:对齐本机相邻 mcpp 仓库已跟踪 HEAD `684a306b`,使正常生成文件一致性检查通过;不是为了绕过检查而使用旧快照。 + +## 已知交互边界 + +- 当前真实 `mcpp add --help` 只有 `--dev`,没有构建依赖或分组迁移参数。构建依赖、已有声明换分组及有歧义的来源会显示准确 TOML 片段,并用“打开 mcpp.toml”完成显式编辑;不会伪造 CLI 参数或自动迁移声明。 +- 跨 registry 的搜索结果缺少完整官方描述符,提供有限预览,不提供一键添加。 +- “重新读取本地”和“更新 mcpp 索引”是不同操作。后者调用真实 CLI,失败时保留可用本地列表。 +- 本机原生 watcher 配额接近上限,因此选中项目同时检查两个文件的内容,确保外部修改仍能更新。详情实际读到的内容也纳入比较基线,避免轮询间隔中短暂出现的 lock 状态残留。不会轮询整个包索引。 + +## 第二轮 review 验证 + +- 真实详情中能看到 `argparse::ArgumentParser` 测试正文,打开完整只读 `.cpp` 源码;本机 252 包中有 179 个匹配官方测试快照。快照来源为官方 mcpp-index 提交 `5fc151e0abed68788d9fa7b47e6e3ccb50607706`。 +- 聚焦搜索输入只有一个清除按钮,清空后按钮隐藏。 +- 实际 Mermaid 图验证三层本地路径依赖、索引 `compat.libpng → compat.zlib`、条件关系和第四层截断,以及复制和缩放。 +- 新增纯逻辑测试覆盖共享节点、循环、开发/构建关系、路径清单、未知来源、规模上限与 Mermaid 标签转义。 +- `data/library-examples.json` 由 `npm run gen:examples` 生成;可通过 `MCPP_INDEX_REPO` 指定 checkout、`MCPP_INDEX_REF` 固定提交,仅提取 Git 已跟踪的测试源文件和清单,不读取构建产物。 +- 离线 Mermaid 和测试快照使 VSIX 从约 414 KB 增至约 1.37 MB,因此 CI 的体积预算从 1 MiB 调整为 2 MiB;仍限制 200 文件并禁止源码、本地 profile、文档目录泄漏,另检查 Mermaid、许可证和示例快照均已打包。 + +![真实索引测试示例](assets/libraries-test-example.png) + +![三层声明依赖图](assets/libraries-dependency-graph.png) + +## 第三轮 review 验证 + +- 代码块不再继承默认内联 code 背景;复制/源码按钮与示例标题在同一行,复制反馈保持图标。 +- 源码统一使用只读虚拟文档,按内容生成不可变身份;真实编辑器输入测试确认不能写入。该机制同时用于本地 checkout 与官方内置示例。 +- 图首屏移除绝对路径、数量段落与长说明,改用颜色区分项目/path/索引节点,节点保留基础标签和 deps 数,连线使用完整 `库名@版本`。 +- Dependencies 只展示直接依赖,命名空间不再误解析为一个包;来源在行内标记。实际点击索引依赖和 path 依赖,均进入同一图面板并高亮对应节点;传递依赖没有混入列表。 +- 核心逻辑测试覆盖 URI 编码后的只读内容恢复、本地内容快照、命名空间直接依赖和图导航身份;真实 UI 验证代码背景、标题同行、源码只读、颜色差异及两类节点定位。 + +- 依赖图支持以鼠标位置为中心的滚轮缩放、按住鼠标拖动画布;保留节点点击高亮,拖动不会误触节点。提供 Fit 复位和 `+` / `-` / `0` 键盘操作。真实 UI 校验缩放锚点、双轴平移及复位。 + + +## 第四轮:共享画布、C++ Modules 与侧栏层级 + +- 设计以方案文档 §14 为准。Basics 内保留 C++ Modules Deps;Dependencies 是其后第 2 个顶层分组,默认折叠,Common commands 随后。默认状态尊重 VS Code 保存的用户偏好;无直接依赖时省略空分组。 +- 真实依赖扩展 0.0.16 的 mcppls CLI 验证 `query module --graph --root … --format json`。实际 fixture 有 `src/main.cpp → demo.l0 → … → demo.l6 → external.api → external.hidden`,以及 `[lib].path="src/l0.cppm"`。 +- 图展示 main 默认入口和 lib 显式入口;3 层显示折叠控件,单支展开下一层,5 层及全部均实际验证。全部内部深度能够看到 demo.l6 和 external.api;external.hidden 始终被外部一层边界过滤,尽管这个 path 包在原始 JSON 中 external=false。 +- 搜索隐藏节点进入聚焦视图,保留直接导入者并可返回项目。点击 demo.l0 在旁边打开只读虚拟源码,真实键盘写入被阻止且原始文件字节未改变;画布保持打开。切换 Packages / C++ Modules 后,选中节点与缩放平移恢复。 +- Mermaid 使用全局 `htmlLabels:false`,模块卡片上的 `+N` 是可鼠标/键盘操作的 SVG 控件。展开只重新投影已有数据,不重复运行 CLI;图布局串行执行,过期请求不覆盖当前项目或视图。 +- 原有 18 项 UI 回归全部通过,新增上述 2 项,共 20 项;扩展宿主五种语言服务变体仍共 40 项通过;完整逻辑/契约测试 709 项通过。VSIX 158 文件、1,450,834 字节,包含共享画布脚本和离线 Mermaid,没有本地 profile/源码/设计文档泄漏。 + +### 性能探测 + +入口 `tools/benchmark-dependency-graphs.mjs`,使用仓库外 Playwright 和离线 Mermaid,在独立 Chromium 中测试 1,000 节点深链、1,000 节点宽扇出、45 节点/450 边共享依赖;分别投影 3 层和全部。 + +| 数据 | 实际渲染上限 | 子图计算 | Mermaid 布局 | +| --- | --- | --- | --- | +| 深链 | 150 节点 / 149 边 | 约 0.43–0.59 ms | 约 52–463 ms | +| 宽扇出 | 150 节点 / 149 边 | 约 0.51–0.70 ms | 约 436–529 ms | +| 共享依赖 | 45 节点 / 300 边 | 约 0.23–0.28 ms | 约 436–488 ms | + +这是本机单次探测结果,不作为跨平台耗时保证。所有可见图都满足 150 节点/300 边预算;原始结果在 gitignored 的 `.dev-profile/libraries-ui-artifacts/graph-performance.json`。 + +### 数据边界 + +- mcppls 此版本全图接口最多输出 500 个模块记录;上游 truncated 或索引尚未完成会显示简短的不完整提示,不声称已展示全部项目数据。截断后缺失的模块名不会被伪造成源码文件。 +- 当前接口不含声明精确行号或逐边的重导出属性,因此跳到准确文件,不自行重解析 C++、猜行号或伪造 export-import 边。 +- 归属按源码最近的项目 manifest 判断;项目边界外的生成源码保守视为外部。更新源码后使用刷新重新查询。 + +![模块入口与深度折叠](assets/libraries-module-graph.png) + +![模块图与旁边只读源码](assets/libraries-module-source-readonly.png) + +## 第五轮:全屏与宽屏详情 + +- 根因:详情 body 被 `max-width:1120px; margin:0 auto` 限制,且双栏固定 2:1,导致全屏时外侧空白大、辅助栏分配过宽。 +- 修正为随编辑器宽度铺开,实际内容边距 16–40px;双栏辅助信息固定在 240–320px,剩余宽度归示例代码。小于 900px 仍为单栏。 +- 本轮仅调整 CSS,运行 `npm run check`、打包以及真实 VS Code 的针对性布局回归。480/800/1200/1600/2560px × 深色/浅色/高对比,共 15 个组合通过;新增断言检查实际边距、代码区占比和辅助栏宽度,同时保留无横向溢出检查。 +- 测量右边距时扣除原生垂直滚动条宽度。UI 驱动的启动检查兼容 VS Code 临时空编辑器,避免误判编辑器数量。 +- 可用 `MCPP_UI_ONLY_LAYOUT=1` 运行针对性布局场景;矩阵记录于 `.dev-profile/libraries-ui-artifacts/layouts.json`。全量功能测试结果见上轮,本次未重复执行不受 CSS 影响的逻辑与语言服务测试。 + +![宽屏包详情](assets/libraries-detail-wide.png) diff --git a/.agnets/docs/2026-10-19-libserial-release-verification.md b/.agnets/docs/2026-10-19-libserial-release-verification.md new file mode 100644 index 0000000..44a69cf --- /dev/null +++ b/.agnets/docs/2026-10-19-libserial-release-verification.md @@ -0,0 +1,16 @@ +# LibSerial 与 0.8.0 release 验收 + +## 合入前实际验证 + +- 环境:Linux,真实 VS Code 1.132.0、mcpp 2026.10.16.3,CLI 日志实际使用 llvm@23.1.3。 +- 独立项目 `.dev-profile/libserial-pr19`,初始清单没有依赖;使用 PR #19 的 VSIX,在真实包详情选择 `compat.libserial@1.0.0`,点击普通 Add。 +- 磁盘清单新增 `[dependencies.compat] libserial = "1.0.0"`,按钮显示 Already declared 且禁用,不是仅执行 CLI 的替代验证。 +- 最小示例以 `` 为第一个 include,构造 `LibSerial::SerialPort` 并检查 `IsOpen()==false`,覆盖头文件、生成 shim、非内联构造/析构与链接。 +- `mcpp build --cache=local -j 2` 成功,实际编译 compat.libserial 与消费者,约 3.93s;`mcpp run --cache=local` 输出 `compat.libserial: SerialPort constructed and closed`,退出码 0。不连接真实串口设备。 +- 可复现入口:`tools/test-libserial-ui.mjs`;UI 截图、清单副本、构建/运行日志位于该项目的 `evidence/`。可通过 `MCPP_TEST_VSIX` 指定 release 下载件,在独立 `MCPP_TEST_PROJECT` / `MCPP_TEST_EXTENSIONS` 中复测;脚本校验安装版本并记录 VSIX SHA-256。 + +## 发布规则 + +- 版本 0.8.0,PR #19 使用 squash 合入,tag 必须指向合入后的 main,匹配 package.json 与 lockfile。 +- tag 工作流运行测试、打包、归档检查、SHA-256,并发布 GitHub Release 与 Open VSX。仓库无 VSCE_PAT,Marketplace 由用户手动上传。 +- 最终交付必须从 GitHub Release 下载 VSIX 与 SHA-256,校验后安装该文件并重新执行 Add/build/run,不以本地重打包件替代。 diff --git a/.agnets/docs/assets/libraries-dependency-graph.png b/.agnets/docs/assets/libraries-dependency-graph.png new file mode 100644 index 0000000..0701520 Binary files /dev/null and b/.agnets/docs/assets/libraries-dependency-graph.png differ diff --git a/.agnets/docs/assets/libraries-dependency-menu.png b/.agnets/docs/assets/libraries-dependency-menu.png new file mode 100644 index 0000000..3f7c99f Binary files /dev/null and b/.agnets/docs/assets/libraries-dependency-menu.png differ diff --git a/.agnets/docs/assets/libraries-detail-wide.png b/.agnets/docs/assets/libraries-detail-wide.png new file mode 100644 index 0000000..9ed97d1 Binary files /dev/null and b/.agnets/docs/assets/libraries-detail-wide.png differ diff --git a/.agnets/docs/assets/libraries-module-graph.png b/.agnets/docs/assets/libraries-module-graph.png new file mode 100644 index 0000000..b851073 Binary files /dev/null and b/.agnets/docs/assets/libraries-module-graph.png differ diff --git a/.agnets/docs/assets/libraries-module-source-readonly.png b/.agnets/docs/assets/libraries-module-source-readonly.png new file mode 100644 index 0000000..9cb8447 Binary files /dev/null and b/.agnets/docs/assets/libraries-module-source-readonly.png differ diff --git a/.agnets/docs/assets/libraries-review.png b/.agnets/docs/assets/libraries-review.png new file mode 100644 index 0000000..6d003cf Binary files /dev/null and b/.agnets/docs/assets/libraries-review.png differ diff --git a/.agnets/docs/assets/libraries-test-example.png b/.agnets/docs/assets/libraries-test-example.png new file mode 100644 index 0000000..5219949 Binary files /dev/null and b/.agnets/docs/assets/libraries-test-example.png differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3200466..20ad12d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,8 +107,12 @@ jobs: COUNT=$(unzip -l "$VSIX" | tail -1 | awk '{print $2}') echo "VSIX: $COUNT files, $((SIZE / 1024)) KiB" if [ "$COUNT" -gt 200 ]; then echo "the VSIX has $COUNT files; something local leaked in"; exit 1; fi - if [ "$SIZE" -gt 1048576 ]; then echo "the VSIX is over 1 MiB ($SIZE bytes)"; exit 1; fi - for forbidden in .dev-profile/ .agents/ test/ tools/ src/ node_modules/; do + # Offline Mermaid and the pinned official tests are shipped assets (~1.37 MB VSIX). + if [ "$SIZE" -gt 2097152 ]; then echo "the VSIX is over 2 MiB ($SIZE bytes)"; exit 1; fi + for required in dist/vendor/mermaid.js dist/vendor/MERMAID-LICENSE dist/data/library-examples.json; do + unzip -p "$VSIX" "extension/$required" > /dev/null + done + for forbidden in .dev-profile/ .agents/ .agnets/ test/ tools/ src/ node_modules/; do if unzip -l "$VSIX" | grep -q "extension/$forbidden"; then echo "leaked into the VSIX: $forbidden" exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac78b91..188e7e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,8 +51,12 @@ jobs: COUNT=$(unzip -l "$VSIX" | tail -1 | awk '{print $2}') echo "VSIX: $COUNT files, $((SIZE / 1024)) KiB" if [ "$COUNT" -gt 200 ]; then echo "文件数异常:$COUNT"; exit 1; fi - if [ "$SIZE" -gt 1048576 ]; then echo "体积异常:$SIZE 字节"; exit 1; fi - for forbidden in .dev-profile/ .agents/ test/ tools/ src/ node_modules/; do + # Budget includes offline Mermaid and the official test example snapshot. + if [ "$SIZE" -gt 2097152 ]; then echo "体积异常(超过 2 MiB):$SIZE 字节"; exit 1; fi + for required in dist/vendor/mermaid.js dist/vendor/MERMAID-LICENSE dist/data/library-examples.json; do + unzip -p "$VSIX" "extension/$required" > /dev/null + done + for forbidden in .dev-profile/ .agents/ .agnets/ test/ tools/ src/ node_modules/; do if unzip -l "$VSIX" | grep -q "extension/$forbidden"; then echo "产物里混入了本地内容:$forbidden" exit 1 diff --git a/.vscodeignore b/.vscodeignore index fbbedb1..483bb46 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -2,6 +2,7 @@ .github/** .gitignore .agents/** +.agnets/** # 本地验收 profile(tools/dev-profile.mjs 生成)与 e2e 的 VS Code 缓存都不属于产物 .dev-profile/** .vscode-test/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d41f52..426dcd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## Unreleased + +## 0.8.0 - 2026-10-19 + +- Package details use the available editor width on fullscreen displays, with bounded side gutters and a compact metadata column. +- Add C++ Modules Deps backed by mcppls JSON, with entry/source cards, one-hop external boundaries and read-only source navigation. Share the package graph canvas with depth/branch folding, hidden-node search, focused views and 150-node/300-edge layout budgets. Move direct Dependencies after Basics as a collapsed sibling. + +- Example code uses a uniform background and compact heading actions; local and bundled example sources both open read-only. Direct dependency rows now resolve namespaces correctly, show package coordinates and sources, and focus the selected node in the simplified colored graph. +- Usage pages restore real index test examples even when the installed index omits tests, using a pinned offline snapshot with full read-only source access. Focused search shows a single clear button. +- Project dependencies offer a Mermaid graph of the nearest three levels, based on manifests and local index metadata, with refresh, zoom and Mermaid copy controls. +- Libraries now defaults to the official `mcpp-index` in `/indices/mcpplibs`, with support for the old location. The xlings tool index is no longer substituted for the library catalog. +- Package details put usage, standard and license tags beside the name, and project selection in the header. A split action chooses project, development or build dependencies. The CLI handles project/development additions; build dependencies and group migrations provide a TOML preview and a manifest editing entry point. +- Search and detail updates preserve the document, selection and scroll state. Cross-registry hits have explicit limited previews. Stale searches and results from another package/project cannot update the current page. +- Dependency state is refreshed independently from cached descriptors, including external file changes. Local rereads and index updates are separate actions; failures retain usable results. + ## 0.7.0 - 2026-10-14 `build.mcpp` is C++ again, compiled by C++ Modules as the build program it is, and the project diff --git a/README.md b/README.md index 403e047..bbcdb4e 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Opening a project never runs `mcpp` on its own and never downloads a toolchain. - **mcpp CLI and tasks** — build, run, test, clean, the quick menu, toolchains: [docs/commands.md](docs/commands.md). - **`mcpp.toml` editing** — structural completion, hovers, go-to-definition, seven structural diagnostics, no formatting: [docs/mcpp-toml.md](docs/mcpp-toml.md). - **`build.mcpp` help** — `build.mcpp` is a `cpp` file that C++ Modules compiles as the build program; this extension adds the `mcpp::…` directive completion, hovers and snippets and the SPEC-007 rule checks no compiler makes: [docs/build-script.md](docs/build-script.md). -- **Libraries** — browse the package index already on this machine, offline; the detail page shows the real example code, the version matrix, and whether the workspace already depends on it. +- **Libraries** — browse the official mcpp-index offline, choose a target project and dependency type, and view available versions and verified example code. Other registry hits have separate previews. See [Libraries](docs/libraries.md). - **Cache views and cleanup** — project artifacts and the shared build cache, previewed before anything is deleted: [docs/cache.md](docs/cache.md). - **C++ Modules** — the other extension's state and actions, in the project view and the status-bar menu, forwarded not reimplemented: [docs/commands.md](docs/commands.md). - **Settings and diagnostics** — a registry-backed settings panel (`mcpp.openSettings`) and a copyable environment self-check (`mcpp.selfCheck`): [docs/settings.md](docs/settings.md). `mcpp.ui.language` overrides our messages and panels only; palette titles always follow VS Code ([docs/architecture.md](docs/architecture.md#language)). diff --git a/data/buildscript-api.json b/data/buildscript-api.json index 3cb51b1..9b2f89d 100644 --- a/data/buildscript-api.json +++ b/data/buildscript-api.json @@ -1,6 +1,6 @@ { - "sourceVersion": "2026.10.11.1", - "sourceCommit": "3fb2d0cc89296c527fcda710792b67bb7a481bed", + "sourceVersion": "2026.10.17.1", + "sourceCommit": "684a306bced037014da08b038412f57c203d0bc9", "protocolVersion": 15, "cacheEpoch": 3, "roles": [ @@ -132,7 +132,7 @@ "mustExist": false, "since": 4, "rules": [], - "docsUrl": "https://github.com/mcpp-community/mcpp/blob/3fb2d0cc89296c527fcda710792b67bb7a481bed/docs/30-build-mcpp.md#runner-how-the-artifact-is-executed-20268192" + "docsUrl": "https://github.com/mcpp-community/mcpp/blob/684a306bced037014da08b038412f57c203d0bc9/docs/30-build-mcpp.md#runner-how-the-artifact-is-executed-20268192" }, { "wire": "runner-named", @@ -263,7 +263,7 @@ "R2.1", "R9.4" ], - "docsUrl": "https://github.com/mcpp-community/mcpp/blob/3fb2d0cc89296c527fcda710792b67bb7a481bed/docs/30-build-mcpp.md#warning-succeeding-and-still-being-heard-20268212" + "docsUrl": "https://github.com/mcpp-community/mcpp/blob/684a306bced037014da08b038412f57c203d0bc9/docs/30-build-mcpp.md#warning-succeeding-and-still-being-heard-20268212" }, { "wire": "diagnostic", @@ -314,7 +314,7 @@ "rules": [ "R2.1" ], - "docsUrl": "https://github.com/mcpp-community/mcpp/blob/3fb2d0cc89296c527fcda710792b67bb7a481bed/docs/30-build-mcpp.md#the-probe-channel-fact-floor-2026952" + "docsUrl": "https://github.com/mcpp-community/mcpp/blob/684a306bced037014da08b038412f57c203d0bc9/docs/30-build-mcpp.md#the-probe-channel-fact-floor-2026952" }, { "wire": "floor", @@ -327,7 +327,7 @@ "rules": [ "R2.1" ], - "docsUrl": "https://github.com/mcpp-community/mcpp/blob/3fb2d0cc89296c527fcda710792b67bb7a481bed/docs/30-build-mcpp.md#the-probe-channel-fact-floor-2026952" + "docsUrl": "https://github.com/mcpp-community/mcpp/blob/684a306bced037014da08b038412f57c203d0bc9/docs/30-build-mcpp.md#the-probe-channel-fact-floor-2026952" }, { "wire": "pack-format", @@ -376,7 +376,7 @@ "R9.2", "R9.5" ], - "docsUrl": "https://github.com/mcpp-community/mcpp/blob/3fb2d0cc89296c527fcda710792b67bb7a481bed/docs/30-build-mcpp.md#deploying-what-the-program-generated-deploy-20269123-protocol-11" + "docsUrl": "https://github.com/mcpp-community/mcpp/blob/684a306bced037014da08b038412f57c203d0bc9/docs/30-build-mcpp.md#deploying-what-the-program-generated-deploy-20269123-protocol-11" }, { "wire": "runtime-search-dir", diff --git a/data/config-registry.json b/data/config-registry.json index 8c2e425..2f08d91 100644 --- a/data/config-registry.json +++ b/data/config-registry.json @@ -783,7 +783,7 @@ "applies": "immediate", "since": "0.6.0", "title": "Path to a mcpp package index checkout", - "description": "Leave it empty to use the indexes under the mcpp home. The home is `$MCPP_HOME`, then `~/.mcpp`; when neither holds an index, the extension asks `mcpp self env --format json` where it is.", + "description": "Leave empty to browse the official mcpp-index at /indices/mcpplibs (legacy registry/data/mcpplibs is supported). MCPP_HOME takes precedence; otherwise the extension asks mcpp self env. Set a directory containing pkgs/ or a collection of indexes to browse a custom source.", "order": 8 }, { @@ -796,7 +796,7 @@ "applies": "immediate", "since": "0.6.0", "title": "Search every configured registry over the network", - "description": "The local index already lists every published package, so this is off by default. Turning it on also searches the other registries with `mcpp search`, which may use the network and can only be parsed on a best-effort basis.", + "description": "Off by default. Also searches other registries with mcpp search; remote results have limited previews and cannot be added directly.", "order": 9 }, { diff --git a/data/i18n/zh-cn.json b/data/i18n/zh-cn.json index a326dd4..020afb3 100644 --- a/data/i18n/zh-cn.json +++ b/data/i18n/zh-cn.json @@ -342,7 +342,7 @@ "Toolchain": "工具链", "Toolchain managed by mcpp": "mcpp 管理的工具链", "Toolchain {0} installed. Choose a global default from the latest list?": "工具链 {0} 安装完成。是否从最新列表中选择全局默认?", - "Unavailable": "不可用", + "Unavailable": "暂无元数据", "Unexpected error: {0}": "发生未预期错误:{0}", "Unknown": "未知", "Use": "用法", @@ -471,5 +471,122 @@ "Only C++ Modules is serving C++": "仅 C++ Modules 在提供 C++ 语言服务", "No other C++ language service (C/C++, clangd, ccls) is active, so no result is shown twice.": "没有其他 C++ 语言服务(C/C++、clangd、ccls)在工作,结果不会重复出现。", "Other C++ language services": "其他 C++ 语言服务", - "They answer the same C++ files, so results appear twice. Click to turn their C++ language features off; C++ Modules asks whether for this workspace or everywhere.": "它们也在处理同一批 C++ 文件,结果会出现两次。点击即可关闭它们的 C++ 语言功能;C++ Modules 会询问只对本工作区还是对所有地方生效。" + "They answer the same C++ files, so results appear twice. Click to turn their C++ language features off; C++ Modules asks whether for this workspace or everywhere.": "它们也在处理同一批 C++ 文件,结果会出现两次。点击即可关闭它们的 C++ 语言功能;C++ Modules 会询问只对本工作区还是对所有地方生效。", + "Libraries": "库", + "Source details": "来源详情", + "Other registries": "其它 registry", + "Set index path": "设置索引路径", + "Update mcpp index": "更新 mcpp 索引", + "Show output": "查看输出", + "Clear search": "清空搜索", + "Search other registries": "搜索其它 registry", + "Declared": "已声明", + "Reread locally": "重新读取本地索引", + "Could not read the index: {0}": "无法读取索引:{0}", + "Searching other registries…": "正在搜索其它 registry…", + "Other registry": "其它 registry", + "Official index": "官方索引", + "Custom index": "自定义索引", + "Browse libraries; select an mcpp project in the detail page to add dependencies.": "可以浏览库;请在详情页选择 mcpp 项目后添加依赖。", + "No local mcpp-index found": "未找到本地 mcpp-index", + "{0} · {1} packages · local": "{0} · {1} 个包 · 本地", + "Target project": "目标项目", + "Choose project": "选择项目", + "Environment platform": "当前环境平台", + "Version": "版本", + "Other platforms (read only)": "其它平台(只读)", + "Resolved in lockfile": "锁定解析", + "Source dependency": "源码依赖", + "No verified import or header path is available locally. See the repository or index site for usage.": "本地没有可验证的模块名或头文件路径,请查看仓库或索引站的用法说明。", + "From the index's example source": "来自索引示例源码", + "Source and diagnostics": "来源与诊断", + "Reading package details…": "正在读取包详情…", + "Adding dependency…": "正在添加依赖…", + "Changing dependency groups is not supported here. Edit mcpp.toml to move the declaration.": "此处暂不支持迁移依赖分组,请编辑 mcpp.toml 移动声明。", + "Retry reading details": "重新读取详情", + "Open mcpp.toml": "打开 mcpp.toml", + "Open source file": "打开源文件", + "Search preview: only the fields returned by mcpp search are available.": "搜索预览:仅展示 mcpp search 返回的字段。", + "Copied": "已复制", + "Copy failed": "复制失败", + "Opened in your browser.": "已在浏览器中打开。", + "Could not open the browser. Link copied to clipboard.": "无法打开浏览器,链接已复制到剪贴板。", + "Could not open the browser or copy the link: {0}": "无法打开浏览器或复制链接:{0}", + "Could not add the dependency. See output for details.": "无法添加依赖,请查看输出了解详情。", + "Could not read package details. See output for details.": "无法读取包详情,请查看输出了解详情。", + "This is a search preview. Set an index path containing this package to read its complete details.": "这是搜索预览。将索引路径设置为包含此包的目录后,可读取完整详情。", + "Search previews cannot be added. Open a complete local descriptor first.": "搜索预览无法直接添加,请先打开完整的本地包描述。", + "The package descriptor is missing. Reread the local index.": "包描述不存在,请重新读取本地索引。", + "Trust this workspace to run mcpp and add dependencies.": "信任此工作区后,才能运行 mcpp 和添加依赖。", + "Select an mcpp project before adding this library.": "请先选择 mcpp 项目,再添加此库。", + "This dependency name is ambiguous. Use a qualified package name in mcpp.toml.": "此依赖名称有歧义,请在 mcpp.toml 中使用完整包名。", + "This project uses a source dependency. Edit its manifest to change the source.": "项目已声明源码依赖,请编辑清单修改来源。", + "Version availability could not be confirmed. Retry reading this descriptor.": "暂时无法确认版本可用性,请重新读取包描述。", + "No version is published for the current environment platform.": "当前环境平台没有可用的发布版本。", + "Already declared": "已声明", + "Descriptor-level dependencies; the selected version may differ. Resolved values refer to this project's lockfile.": "这里展示包描述中的依赖,所选版本的依赖可能不同;解析版本来自当前项目的锁文件。", + "mcpp add failed (exit {0}). See output for details.": "mcpp add 失败(退出码 {0}),请查看输出了解详情。", + "Choose the target mcpp project": "选择目标 mcpp 项目", + "Dependencies will be added to this project's mcpp.toml": "依赖将添加到此项目的 mcpp.toml", + "Updating the global mcpp index; a configured custom checkout is only reread.": "正在更新全局 mcpp 索引;已配置的自定义检出目录仅会被重新读取。", + "The current environment platform is not recognized by this index.": "索引无法识别当前环境平台。", + "Versions are from descriptor text; mcpp will confirm availability when adding.": "版本来自描述符文本,添加时将由 mcpp 确认可用性。", + "Platforms and versions": "平台与版本", + "Add dependency": "添加依赖", + "Usage": "用法", + "Dependency type": "依赖类型", + "Project dependency": "项目依赖", + "Development dependency": "开发依赖", + "Build dependency": "构建依赖", + "Add development dependency": "添加开发依赖", + "Build dependencies are used by the build script. Copy this entry into mcpp.toml; mcpp add does not support this group yet.": "构建依赖供构建脚本使用;当前 mcpp add 尚不支持此分组,请复制此项并编辑 mcpp.toml。", + "Changing this declaration requires a manifest edit. Copy the entry and update its dependency group in mcpp.toml.": "请复制声明并在 mcpp.toml 中调整依赖分组;已有声明不会自动迁移。", + "The dependency declaration changed. Review mcpp.toml before adding it.": "依赖声明已变化,请先检查 mcpp.toml 再添加。", + "Updating mcpp index…": "正在更新 mcpp 索引…", + "Index update failed; showing the last available local snapshot.": "索引更新失败,继续显示上次可用的本地快照。", + "Index updated; {0} local packages.": "索引已更新,本地共 {0} 个包。", + "Bundled official index tests · commit {0}": "插件内置官方索引测试 · 提交 {0}", + "Project dependency graph": "项目依赖图", + "Copy Mermaid": "复制 Mermaid", + "Up to 3 dependency levels · {0} packages · {1} edges": "最多 3 层依赖 · {0} 个包 · {1} 条关系", + "Edges come from manifests and local index descriptors; lockfile versions are shown when available. This is a declaration graph, not a resolved build graph. Dashed nodes have no local dependency metadata; conditional edges may not apply to this environment.": "关系来自项目清单和本地索引描述符;有锁定版本时显示锁定版本。这是声明关系图,不代表实际构建解析结果。虚线节点缺少本地依赖元数据,条件关系可能不适用于当前环境。", + "Large graph truncated at 120 nodes or 240 edges.": "大型依赖图已截断(最多 120 个节点或 240 条关系)。", + "Fit": "适应窗口", + "Mermaid source": "Mermaid 源码", + "Could not render the graph. Copy Mermaid source or refresh to retry.": "依赖图渲染失败,可复制 Mermaid 源码或刷新重试。", + "Could not read project dependencies: {0}": "无法读取项目依赖:{0}", + "Open an mcpp project first.": "请先打开一个 mcpp 项目。", + "The project manifest is missing or unreadable.": "项目清单不存在或无法读取。", + "Reading project dependencies…": "正在读取项目依赖…", + "Source": "源码", + "Open read-only source": "打开只读源码", + "Dependency graph": "依赖图", + "Maximum dependency depth: 3": "最多展示 3 层依赖", + "Zoom out": "缩小", + "Zoom in": "放大", + "Legend and source": "图例与源码", + "Project": "项目", + "Declaration graph · dashed edges are conditional · lockfile supplies versions": "声明关系图 · 虚线为条件依赖 · 锁文件提供版本", + "index": "索引", + "build": "构建", + "conditional": "条件", + "Scroll to zoom · drag to pan": "滚轮缩放 · 按住拖动画布", + "C++ Modules Deps": "C++ Modules 依赖图", + "No module relationships were found for this project.": "此项目尚无模块关系。", + "View limit reached. Focus a node to explore its dependencies.": "已达到当前视图上限,可聚焦节点继续查看。", + "The source graph is incomplete. Refresh after indexing finishes.": "数据尚不完整,可在索引完成后刷新。", + "Expand next level": "展开下一层", + "Selected node": "已选节点", + "Graph view": "图视图", + "Packages": "包依赖", + "Depth": "深度", + "Find a node…": "查找节点…", + "Back to project": "返回项目", + "Select a node to explore its relationships.": "选择节点查看关联。", + "Collapse": "收起", + "Focus": "聚焦", + "Packages show declarations; modules show mcppls imports. External modules stop after one hop.": "包图显示声明关系;模块图来自 mcppls,外部模块只显示一层。", + "Source is unavailable for this module.": "此模块的源码不可用。", + "Choose a module source file": "选择模块源码文件", + "The source graph is incomplete. Some modules may be omitted.": "源图数据不完整,部分模块可能尚未包含。" } diff --git a/data/library-examples.json b/data/library-examples.json new file mode 100644 index 0000000..4752397 --- /dev/null +++ b/data/library-examples.json @@ -0,0 +1,1880 @@ +{ + "repository": "https://github.com/mcpp-community/mcpp-index", + "commit": "5fc151e0abed68788d9fa7b47e6e3ccb50607706", + "projects": [ + { + "project": "abseil", + "text": "# abseil test project: consumes compat.abseil and asserts behavior under\n# `mcpp test`. Part of the mcpp-index self-referential workspace — the\n# workspace-root `[indices]` redirect points at this repo, so the dependency\n# resolves to the checked-in recipe (pkgs/c/compat.abseil.lua) rather than the\n# published remote index.\n#\n# The test deliberately exercises symbols that live in COMPILED TUs\n# (absl::StrCat, absl::Status, absl::Mutex, absl::Cord, absl::FormatTime),\n# not just header inlines: a green build that linked zero Abseil objects would\n# otherwise look identical to a working one.\n[package]\nname = \"abseil-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nabseil = \"20250512.1\"\n", + "files": [ + { + "path": "tests/examples/abseil/tests/basics.cpp", + "text": "// Behavioral test for compat.abseil.\n//\n// Every assertion below crosses into a COMPILED Abseil translation unit, on\n// purpose — header-only usage would link zero objects from the package and\n// still go green, which is exactly the failure this suite has to catch:\n//\n// absl::StrCat / StrSplit / StrJoin -> absl/strings/*.cc\n// absl::Status / StatusOr -> absl/status/status.cc, statusor.cc\n// absl::Mutex / Notification -> absl/synchronization/mutex.cc\n// absl::Cord -> absl/strings/cord.cc + cord_rep_*.cc\n// absl::FormatTime / ParseTime -> absl/time/*.cc + internal/cctz/src/*.cc\n// absl::flat_hash_map -> absl/container/internal/raw_hash_set.cc\n// absl::StrFormat -> absl/strings/internal/str_format/*.cc\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n#include \n\n#include \"absl/container/flat_hash_map.h\"\n#include \"absl/status/status.h\"\n#include \"absl/status/statusor.h\"\n#include \"absl/strings/cord.h\"\n#include \"absl/strings/str_cat.h\"\n#include \"absl/strings/str_format.h\"\n#include \"absl/strings/str_join.h\"\n#include \"absl/strings/str_split.h\"\n#include \"absl/synchronization/mutex.h\"\n#include \"absl/synchronization/notification.h\"\n#include \"absl/time/civil_time.h\"\n#include \"absl/time/time.h\"\n\nnamespace {\n\nbool strings_ok() {\n if (absl::StrCat(\"answer=\", 42, \"/\", 1.5) != \"answer=42/1.5\") return false;\n if (absl::StrFormat(\"%s:%04d:%#x\", \"id\", 7, 255) != \"id:0007:0xff\") return false;\n\n const std::vector parts = absl::StrSplit(\"a,b,,c\", ',');\n if (parts.size() != 4 || parts[2] != \"\") return false;\n if (absl::StrJoin(parts, \"|\") != \"a|b||c\") return false;\n return true;\n}\n\nbool cord_ok() {\n // Build a Cord large enough to leave the inline representation and go\n // through the btree reps in absl/strings/internal/cord_rep_btree*.cc.\n absl::Cord cord;\n for (int i = 0; i < 512; ++i) cord.Append(absl::StrCat(\"chunk\", i, \";\"));\n const std::string flat(cord);\n return cord.size() == flat.size() && flat.rfind(\"chunk511;\") != std::string::npos;\n}\n\nbool status_ok() {\n const absl::Status ok = absl::OkStatus();\n const absl::Status err = absl::InvalidArgumentError(\"bad input\");\n if (!ok.ok() || err.ok()) return false;\n if (err.code() != absl::StatusCode::kInvalidArgument) return false;\n if (err.message() != \"bad input\") return false;\n // ToString() formats through absl/status/status.cc.\n if (err.ToString().find(\"INVALID_ARGUMENT\") == std::string::npos) return false;\n\n const absl::StatusOr good = 11;\n const absl::StatusOr bad = absl::NotFoundError(\"missing\");\n return good.ok() && *good == 11 && !bad.ok();\n}\n\nbool time_ok() {\n // A fixed instant, formatted in UTC: exercises the vendored cctz sources.\n const absl::TimeZone utc = absl::UTCTimeZone();\n const absl::Time t = absl::FromCivil(absl::CivilSecond(2026, 8, 4, 12, 34, 56), utc);\n if (absl::FormatTime(\"%Y-%m-%dT%H:%M:%S\", t, utc) != \"2026-08-04T12:34:56\") return false;\n\n absl::Time parsed;\n std::string err;\n if (!absl::ParseTime(\"%Y-%m-%d\", \"2026-08-04\", utc, &parsed, &err)) return false;\n return absl::ToUnixSeconds(t) - absl::ToUnixSeconds(parsed) == 12 * 3600 + 34 * 60 + 56;\n}\n\nbool concurrency_ok() {\n // absl::Mutex + Notification pull in the real synchronization objects\n // (mutex.cc, waiter backends, thread identity).\n absl::Mutex mu;\n int counter = 0;\n absl::Notification started;\n\n std::thread worker([&] {\n started.WaitForNotification();\n for (int i = 0; i < 1000; ++i) {\n absl::MutexLock lock(&mu);\n ++counter;\n }\n });\n\n started.Notify();\n for (int i = 0; i < 1000; ++i) {\n absl::MutexLock lock(&mu);\n ++counter;\n }\n worker.join();\n\n absl::MutexLock lock(&mu);\n return counter == 2000;\n}\n\nbool container_ok() {\n absl::flat_hash_map m;\n for (int i = 0; i < 256; ++i) m.emplace(absl::StrCat(\"k\", i), i);\n if (m.size() != 256) return false;\n const auto it = m.find(\"k255\");\n return it != m.end() && it->second == 255;\n}\n\n} // namespace\n\nint main() {\n const bool ok = strings_ok() && cord_ok() && status_ok() && time_ok()\n && concurrency_ok() && container_ok();\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "archive", + "text": "# Archive + compression codecs: libarchive, zlib, bzip2, lz4, xz (lzma), zstd.\n# Headless: version probes + compress→decompress round-trips. Cross-platform.\n[package]\nname = \"archive-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nlibarchive = \"3.8.7\"\nzlib = \"1.3.2\"\nbzip2 = \"1.0.8\"\nlz4 = \"1.10.0\"\nxz = \"5.8.3\"\nzstd = \"1.5.7\"\n", + "files": [ + { + "path": "tests/examples/archive/tests/compression.cpp", + "text": "// Round-trip compress→decompress per codec, asserting byte-for-byte recovery.\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nstatic bool same_bytes(const void* a, size_t an, const void* b, size_t bn) {\n return an == bn && std::memcmp(a, b, bn) == 0;\n}\n\nint main() {\n const uint8_t input[] = \"mcpp compat compression smoke\";\n const size_t input_size = sizeof(input) - 1;\n\n uint8_t zc[256] = {}; uLongf zcs = sizeof(zc);\n if (compress2(zc, &zcs, input, (uLong)input_size, Z_BEST_SPEED) != Z_OK) return 1;\n uint8_t zo[sizeof(input)] = {}; uLongf zos = input_size;\n if (uncompress(zo, &zos, zc, zcs) != Z_OK || !same_bytes(zo, zos, input, input_size)) return 2;\n\n char bc[256] = {}; unsigned int bcs = sizeof(bc);\n if (BZ2_bzBuffToBuffCompress(bc, &bcs, const_cast(reinterpret_cast(input)),\n (unsigned)input_size, 1, 0, 30) != BZ_OK) return 3;\n char bo[sizeof(input)] = {}; unsigned int bos = input_size;\n if (BZ2_bzBuffToBuffDecompress(bo, &bos, bc, bcs, 0, 0) != BZ_OK ||\n !same_bytes(bo, bos, input, input_size)) return 4;\n\n char lc[256] = {};\n const int lcs = LZ4_compress_default(reinterpret_cast(input), lc, (int)input_size, sizeof(lc));\n if (lcs <= 0) return 5;\n char lo[sizeof(input)] = {};\n const int los = LZ4_decompress_safe(lc, lo, lcs, sizeof(lo));\n if (los < 0 || !same_bytes(lo, (size_t)los, input, input_size)) return 6;\n\n std::vector sc(ZSTD_compressBound(input_size));\n const size_t scs = ZSTD_compress(sc.data(), sc.size(), input, input_size, 1);\n if (ZSTD_isError(scs)) return 7;\n std::vector so(input_size);\n const size_t sos = ZSTD_decompress(so.data(), so.size(), sc.data(), scs);\n if (ZSTD_isError(sos) || !same_bytes(so.data(), sos, input, input_size)) return 8;\n\n std::vector xc(lzma_stream_buffer_bound(input_size)); size_t xcp = 0;\n if (lzma_easy_buffer_encode(0, LZMA_CHECK_CRC64, nullptr, input, input_size,\n xc.data(), &xcp, xc.size()) != LZMA_OK) return 9;\n uint64_t xm = (std::numeric_limits::max)(); size_t xip = 0, xop = 0;\n std::vector xo(input_size);\n if (lzma_stream_buffer_decode(&xm, 0, nullptr, xc.data(), &xip, xcp, xo.data(), &xop, xo.size()) != LZMA_OK ||\n !same_bytes(xo.data(), xop, input, input_size)) return 10;\n return 0;\n}\n" + }, + { + "path": "tests/examples/archive/tests/versions.cpp", + "text": "// libarchive object lifecycle + version probes across all codecs.\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nint main() {\n archive* writer = archive_write_new();\n if (!writer) return 1;\n archive_write_free(writer);\n archive_entry* entry = archive_entry_new();\n if (!entry) return 2;\n archive_entry_free(entry);\n if (!archive_version_string()) return 3;\n if (!zlibVersion()) return 4;\n if (!BZ2_bzlibVersion()) return 5;\n if (LZ4_versionNumber() <= 0) return 6;\n if (ZSTD_versionNumber() == 0) return 7;\n if (lzma_version_number() == 0) return 8;\n return 0;\n}\n" + } + ] + }, + { + "project": "argparse", + "text": "# compat.argparse test project. Header-only; `compat` redirect inherited from\n# the workspace root.\n[package]\nname = \"argparse-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nargparse = \"3.2\"\n", + "files": [ + { + "path": "tests/examples/argparse/tests/parse.cpp", + "text": "// Behavioral test — compat.argparse parses a command line, it does not merely\n// include.\n//\n// argv is written by hand rather than taken from the runner, so the assertions\n// are about argparse's behaviour and not about how `mcpp test` invokes a\n// binary. No `import std;`: argparse.hpp is a textual header over the standard\n// library.\n#include \n\n#include \n#include \n#include \n\nint main() {\n argparse::ArgumentParser program(\"renderer\", \"0.1.0\");\n program.add_argument(\"model\").help(\"path to the glTF model\");\n program.add_argument(\"--width\").default_value(1280).scan<'i', int>();\n program.add_argument(\"--validate\").default_value(false).implicit_value(true);\n\n const std::vector argv{\"renderer\", \"scene.gltf\", \"--width\", \"1920\", \"--validate\"};\n try {\n program.parse_args(argv);\n } catch (const std::exception& e) {\n std::cerr << \"parse_args threw: \" << e.what() << \"\\n\";\n return 1;\n }\n\n if (program.get(\"model\") != \"scene.gltf\") { std::cerr << \"positional wrong\\n\"; return 2; }\n if (program.get(\"--width\") != 1920) { std::cerr << \"typed scan wrong\\n\"; return 3; }\n if (!program.get(\"--validate\")) { std::cerr << \"implicit flag wrong\\n\"; return 4; }\n\n // The default has to survive an argv that never mentions the option.\n argparse::ArgumentParser defaults(\"renderer\");\n defaults.add_argument(\"--width\").default_value(1280).scan<'i', int>();\n defaults.parse_args(std::vector{\"renderer\"});\n if (defaults.get(\"--width\") != 1280) { std::cerr << \"default_value wrong\\n\"; return 5; }\n\n // A missing required positional must be an error, not a silent empty\n // string — that is the half of a parser a \"compiles fine\" test misses.\n argparse::ArgumentParser required(\"renderer\");\n required.add_argument(\"model\");\n bool threw = false;\n try {\n required.parse_args(std::vector{\"renderer\"});\n } catch (const std::exception&) {\n threw = true;\n }\n if (!threw) { std::cerr << \"missing positional did not throw\\n\"; return 6; }\n\n std::cout << \"compat.argparse: ok (positional, typed scan, implicit flag, default, error path)\\n\";\n return 0;\n}\n" + } + ] + }, + { + "project": "asio-module", + "text": "# Asio C++23-module consumer test project: `import std; import asio;`.\n# The public package is `chriskohlhoff.asio@1.38.1` (module-only).\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `chriskohlhoff`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nchriskohlhoff = { path = \"../../..\" }\n\n[package]\nname = \"asio-module-tests\"\nversion = \"0.1.0\"\n\n[dependencies.chriskohlhoff]\nasio = \"1.38.1\"\n\n[dependencies.fmtlib]\nfmt = \"12.2.0\"\n", + "files": [ + { + "path": "tests/examples/asio-module/tests/core.cpp", + "text": "// Core executor/timer behavior over the module surface: strand FIFO order,\n// work-guard-driven run loop, thread_pool, and timer cancellation mapping to\n// asio::error::operation_aborted. Module consumers pair `import asio;` with\n// `import std;` (no text #include mixing).\nimport std;\nimport asio;\n\nint main() {\n using namespace std::chrono_literals;\n\n asio::io_context io;\n auto guard = asio::make_work_guard(io);\n auto serial = asio::make_strand(io);\n asio::steady_timer timer(io, 2ms);\n\n std::atomic posted{0};\n std::mutex order_mutex;\n std::vector order;\n bool timer_called = false;\n\n asio::post(serial, [&] {\n std::lock_guard lock(order_mutex);\n order.push_back(1);\n ++posted;\n });\n asio::post(serial, [&] {\n std::lock_guard lock(order_mutex);\n order.push_back(2);\n ++posted;\n });\n timer.async_wait([&](const std::error_code& ec) {\n timer_called = !ec;\n guard.reset();\n });\n\n std::thread worker([&] { io.run(); });\n worker.join();\n if (posted != 2 || !timer_called || order != std::vector{1, 2}) return 1;\n\n asio::thread_pool pool(2);\n std::atomic pooled{0};\n asio::post(pool, [&] { ++pooled; });\n asio::post(pool, [&] { ++pooled; });\n pool.join();\n if (pooled != 2) return 2;\n\n asio::io_context cancel_io;\n asio::steady_timer cancelled(cancel_io, 1h);\n asio::cancellation_signal cancellation;\n std::error_code cancelled_ec;\n bool cancelled_called = false;\n cancelled.async_wait(asio::bind_cancellation_slot(\n cancellation.slot(),\n [&](const std::error_code& ec) {\n cancelled_ec = ec;\n cancelled_called = true;\n }));\n cancellation.emit(asio::cancellation_type::all);\n cancel_io.run();\n\n return cancelled_called && cancelled_ec == asio::error::operation_aborted ? 0 : 3;\n}\n" + }, + { + "path": "tests/examples/asio-module/tests/coroutine.cpp", + "text": "// Coroutine surface: awaitable/co_spawn/use_awaitable/this_coro over the\n// module boundary, including a real timer suspension point.\nimport std;\nimport asio;\n\nasio::awaitable answer() {\n auto ex = co_await asio::this_coro::executor;\n asio::steady_timer t(ex, std::chrono::milliseconds(1));\n co_await t.async_wait(asio::use_awaitable);\n co_return 42;\n}\n\nint main() {\n asio::io_context io;\n int result = 0;\n asio::co_spawn(io, answer(), [&](std::exception_ptr, int v) { result = v; });\n io.run();\n return result == 42 ? 0 : 1;\n}\n" + }, + { + "path": "tests/examples/asio-module/tests/errors.cpp", + "text": "// The error surface: the four Asio error enums and their enumerators reach a\n// module consumer, and each one compares equal to the std::error_code that\n// asio::error::make_error_code builds from it.\n//\n// Before this was exported the module offered exactly one condition,\n// operation_aborted, which is enough to notice a cancellation and nothing else:\n// telling a refused connection from a DNS failure had no name to compare\n// against on the consumer's side.\nimport std;\nimport asio;\n\nint main() {\n // --- the enum types themselves ---\n static_assert(std::is_enum_v);\n static_assert(std::is_enum_v);\n static_assert(std::is_enum_v);\n static_assert(std::is_enum_v);\n\n // --- enumerators, one per enum, round-tripped through make_error_code ---\n const std::error_code aborted = asio::error::make_error_code(asio::error::operation_aborted);\n const std::error_code refused = asio::error::make_error_code(asio::error::connection_refused);\n const std::error_code timedout = asio::error::make_error_code(asio::error::timed_out);\n const std::error_code unreach = asio::error::make_error_code(asio::error::host_unreachable);\n const std::error_code netdown = asio::error::make_error_code(asio::error::network_unreachable);\n const std::error_code nohost = asio::error::make_error_code(asio::error::host_not_found);\n const std::error_code retryhost = asio::error::make_error_code(asio::error::host_not_found_try_again);\n const std::error_code nosvc = asio::error::make_error_code(asio::error::service_not_found);\n const std::error_code eof = asio::error::make_error_code(asio::error::eof);\n\n // An error_code built from an enumerator compares equal to that enumerator:\n // this is the comparison real code writes (`if (ec == asio::error::eof)`),\n // and it only works if the enumerator NAME crossed the module boundary.\n if (aborted != asio::error::operation_aborted) return 1;\n if (refused != asio::error::connection_refused) return 2;\n if (timedout != asio::error::timed_out) return 3;\n if (unreach != asio::error::host_unreachable) return 4;\n if (netdown != asio::error::network_unreachable) return 5;\n if (nohost != asio::error::host_not_found) return 6;\n if (retryhost != asio::error::host_not_found_try_again) return 7;\n if (nosvc != asio::error::service_not_found) return 8;\n if (eof != asio::error::eof) return 9;\n\n // Distinct conditions must not collapse into each other.\n if (refused == timedout || nohost == retryhost) return 10;\n\n // Every one of them is a real, non-empty condition.\n for (const auto& ec : {aborted, refused, timedout, unreach, netdown,\n nohost, retryhost, nosvc, eof}) {\n if (!ec) return 11;\n if (ec.message().empty()) return 12;\n }\n\n // --- ip::make_address parses, and reports failure through error_code ---\n std::error_code parse;\n const auto v4 = asio::ip::make_address(\"192.0.2.7\", parse);\n if (parse || !v4.is_v4() || v4.to_string() != \"192.0.2.7\") return 13;\n\n const auto v6 = asio::ip::make_address(\"::1\", parse);\n if (parse || !v6.is_v6() || !v6.is_loopback()) return 14;\n\n (void)asio::ip::make_address(\"not-an-address\", parse);\n if (!parse) return 15;\n\n if (asio::ip::make_address_v4(\"10.0.0.1\", parse).to_string() != \"10.0.0.1\" || parse) return 16;\n if (!asio::ip::make_address_v6(\"::1\", parse).is_loopback() || parse) return 17;\n\n return 0;\n}\n" + }, + { + "path": "tests/examples/asio-module/tests/experimental.cpp", + "text": "// Experimental channel/concurrent_channel/use_promise over the module surface.\nimport std;\nimport asio;\n\nint main() {\n asio::io_context io;\n\n asio::experimental::channel ch(io, 1);\n if (!ch.try_send(std::error_code{}, \"channel\")) return 1;\n std::string channel_value;\n std::error_code channel_error;\n ch.async_receive([&](std::error_code ec, std::string value) {\n channel_error = ec;\n channel_value = std::move(value);\n });\n\n asio::experimental::concurrent_channel concurrent(io, 1);\n if (!concurrent.try_send(std::error_code{}, 42)) return 2;\n int concurrent_value = 0;\n std::error_code concurrent_error;\n concurrent.async_receive([&](std::error_code ec, int value) {\n concurrent_error = ec;\n concurrent_value = value;\n });\n\n auto promised = asio::post(io, asio::experimental::use_promise);\n bool promise_completed = false;\n promised([&] { promise_completed = true; });\n\n io.run();\n\n return !channel_error && channel_value == \"channel\"\n && !concurrent_error && concurrent_value == 42\n && promise_completed ? 0 : 3;\n}\n" + }, + { + "path": "tests/examples/asio-module/tests/file.cpp", + "text": "// Asio's file I/O over the module surface: asio::stream_file writes and reads\n// back through a coroutine, asio::random_access_file reads at an offset, and\n// asio::file_base's open flags compose.\n//\n// These names are exported only where Asio has a file backend. For THIS\n// package's configuration the condition is exactly Windows: the backend is IOCP\n// (ASIO_HAS_WINDOWS_RANDOM_ACCESS_HANDLE), and the only other one Asio offers is\n// io_uring, which this package does not enable. `ASIO_HAS_FILE` itself is\n// unusable as the discriminator here — a macro does not cross a module boundary\n// and a module consumer includes no Asio header, so it is always false in this\n// TU no matter what the package was built with.\nimport std;\nimport asio;\n\n#if defined(_WIN32)\n\nint main() {\n namespace fs = std::filesystem;\n\n const auto path = (fs::temp_directory_path() / \"mcpp_asio_module_file.bin\").string();\n std::error_code rm;\n fs::remove(path, rm);\n\n constexpr std::string_view payload = \"mcpp-asio-file\";\n\n asio::io_context io;\n int failure = 0;\n\n asio::co_spawn(io, [&]() -> asio::awaitable {\n // --- write through stream_file, composing three file_base flags ---\n {\n asio::stream_file out(co_await asio::this_coro::executor, path,\n asio::file_base::write_only\n | asio::file_base::create\n | asio::file_base::truncate);\n const auto written =\n co_await asio::async_write(out, asio::buffer(payload), asio::use_awaitable);\n if (written != payload.size()) { failure = 1; co_return; }\n out.close();\n }\n\n // --- read it back through stream_file ---\n {\n asio::stream_file in(co_await asio::this_coro::executor, path,\n asio::file_base::read_only);\n std::string got(payload.size(), '\\0');\n const auto read =\n co_await asio::async_read(in, asio::buffer(got), asio::use_awaitable);\n if (read != payload.size() || got != payload) { failure = 2; co_return; }\n }\n\n // --- random_access_file reads at an offset, leaving no file position ---\n {\n asio::random_access_file ra(co_await asio::this_coro::executor, path,\n asio::file_base::read_only);\n std::string tail(5, '\\0'); // \"-file\", offset 9\n const auto read = co_await ra.async_read_some_at(9, asio::buffer(tail),\n asio::use_awaitable);\n if (read != 5 || tail != \"-file\") { failure = 3; co_return; }\n }\n }, asio::detached);\n\n io.run();\n fs::remove(path, rm);\n\n // The aliases are the basic_ templates with the default executor.\n static_assert(std::is_same_v>);\n static_assert(std::is_same_v>);\n static_assert(std::is_base_of_v);\n\n return failure;\n}\n\n#else\n\nint main() {\n // No file backend in this configuration: asserting the names are absent is\n // not something a TU can do, so this leg only records that the test ran.\n std::println(\"asio module: file I/O not available in this configuration\");\n return 0;\n}\n\n#endif\n" + }, + { + "path": "tests/examples/asio-module/tests/network.cpp", + "text": "// TCP (acceptor/socket, async_read/async_write), the free asio::async_connect\n// over a sequence of candidate endpoints, and UDP (datagram send/receive) over\n// the imported module surface.\nimport std;\nimport asio;\n\nint main() {\n using namespace std::chrono_literals;\n\n // --- TCP echo ---\n asio::io_context io;\n asio::ip::tcp::acceptor acceptor(io, {asio::ip::address_v4::loopback(), 0});\n asio::ip::tcp::socket server(io);\n asio::ip::tcp::socket client(io);\n asio::steady_timer deadline(io, 5s);\n\n const std::string ping = \"ping\";\n const std::string pong = \"pong\";\n std::array server_data{};\n std::array client_data{};\n bool accepted = false;\n bool connected = false;\n bool tcp_done = false;\n bool timed_out = false;\n int failure = 0;\n\n auto fail = [&](int code) {\n if (failure == 0) failure = code;\n std::error_code ignored;\n acceptor.close(ignored);\n server.close(ignored);\n client.close(ignored);\n deadline.cancel();\n };\n\n deadline.async_wait([&](const std::error_code& ec) {\n if (!ec) {\n timed_out = true;\n fail(90);\n }\n });\n\n acceptor.async_accept(server, [&](const std::error_code& ec) {\n if (ec) return fail(1);\n accepted = true;\n asio::async_read(server, asio::buffer(server_data),\n [&](const std::error_code& read_ec, std::size_t n) {\n if (read_ec || n != ping.size()\n || std::string(server_data.data(), n) != ping) return fail(2);\n asio::async_write(server, asio::buffer(pong),\n [&](const std::error_code& write_ec, std::size_t written) {\n if (write_ec || written != pong.size()) fail(3);\n });\n });\n });\n\n client.async_connect(\n {asio::ip::address_v4::loopback(), acceptor.local_endpoint().port()},\n [&](const std::error_code& ec) {\n if (ec) return fail(4);\n connected = true;\n asio::async_write(client, asio::buffer(ping),\n [&](const std::error_code& write_ec, std::size_t written) {\n if (write_ec || written != ping.size()) return fail(5);\n asio::async_read(client, asio::buffer(client_data),\n [&](const std::error_code& read_ec, std::size_t n) {\n if (read_ec || n != pong.size()\n || std::string(client_data.data(), n) != pong) return fail(6);\n tcp_done = true;\n deadline.cancel();\n });\n });\n });\n\n io.run();\n if (failure || timed_out || !accepted || !connected || !tcp_done) return failure ? failure : 7;\n\n // --- UDP datagram ---\n asio::io_context udp_io;\n asio::ip::udp::socket receiver(udp_io, {asio::ip::address_v4::loopback(), 0});\n asio::ip::udp::socket sender(udp_io, {asio::ip::address_v4::loopback(), 0});\n const std::string datagram = \"asio-udp\";\n std::array received{};\n asio::ip::udp::endpoint remote;\n bool receive_done = false;\n bool send_done = false;\n std::error_code udp_failure;\n\n receiver.async_receive_from(asio::buffer(received), remote,\n [&](const std::error_code& ec, std::size_t n) {\n udp_failure = ec;\n receive_done = !ec && n == datagram.size()\n && std::string(received.data(), n) == datagram;\n });\n sender.async_send_to(asio::buffer(datagram), receiver.local_endpoint(),\n [&](const std::error_code& ec, std::size_t n) {\n if (ec) udp_failure = ec;\n send_done = !ec && n == datagram.size();\n });\n\n udp_io.run();\n if (udp_failure || !receive_done || !send_done) return 8;\n\n // --- free asio::async_connect over a candidate sequence ---\n //\n // The member socket.async_connect above takes ONE endpoint; the free\n // function takes a range and tries each in turn, which is what a consumer\n // hands a resolver's results_type to. Assert that semantics directly:\n // an unreachable candidate first, the live acceptor second. Kept hermetic\n // (no DNS) so it cannot fail for reasons that have nothing to do with the\n // module surface.\n asio::io_context connect_io;\n asio::ip::tcp::acceptor live(connect_io, {asio::ip::address_v4::loopback(), 0});\n asio::ip::tcp::socket accepted_side(connect_io);\n asio::ip::tcp::socket connecting(connect_io);\n live.async_accept(accepted_side, [](const std::error_code&) {});\n\n const std::vector candidates{\n // Port 1 on loopback: nothing listens there, so this candidate is\n // refused and async_connect must move on rather than give up.\n {asio::ip::address_v4::loopback(), 1},\n live.local_endpoint(),\n };\n\n std::error_code connect_ec{std::make_error_code(std::errc::not_supported)};\n asio::ip::tcp::endpoint chosen;\n bool connect_done = false;\n\n asio::async_connect(connecting, candidates,\n [&](const std::error_code& ec, const asio::ip::tcp::endpoint& ep) {\n connect_ec = ec;\n chosen = ep;\n connect_done = true;\n });\n\n connect_io.run();\n if (!connect_done || connect_ec) return 9;\n if (chosen != live.local_endpoint()) return 10; // it must have skipped port 1\n if (!connecting.is_open()) return 11;\n\n return 0;\n}\n" + }, + { + "path": "tests/examples/asio-module/tests/surface.cpp", + "text": "// Surface/smoke coverage: exported types, completion tokens, and vocabulary\n// types are usable across the module boundary.\nimport std;\nimport fmt;\nimport asio;\n\nint main() {\n // 回归:导入 Asio 后,libc++ 的 println 仍应保持可用。\n std::println(\"asio module println\");\n std::println(\"asio module println {}\", 42);\n\n // --- buffer / address (existing) ---\n std::array src{'m', 'c', 'p', 'p'};\n asio::const_buffer cb = asio::buffer(src);\n if (cb.size() != 4) return 1;\n\n std::string dst(4, '\\0');\n asio::mutable_buffer mb = asio::buffer(dst);\n std::memcpy(mb.data(), cb.data(), cb.size());\n if (dst != \"mcpp\") return 2;\n\n asio::ip::address_v4 loopback = asio::ip::address_v4::loopback();\n if (loopback.to_string() != \"127.0.0.1\") return 3;\n\n asio::ip::tcp::endpoint ep(loopback, 8080);\n if (ep.port() != 8080) return 4;\n\n // --- execution context hierarchy ---\n if (!std::is_base_of_v) return 5;\n if (!std::is_base_of_v) return 6;\n if (!std::is_base_of_v) return 7;\n\n // --- executors ---\n static_assert(std::is_class_v);\n static_assert(std::is_class_v);\n\n // --- error_code typedef ---\n static_assert(std::is_same_v);\n\n // --- cancellation_type (scoped enum) ---\n static_assert(std::is_enum_v);\n if (static_cast(asio::cancellation_type::all) == 0) return 8;\n if (static_cast(asio::cancellation_type::terminal) == 0) return 9;\n\n // --- signal / timer types ---\n static_assert(std::is_class_v);\n static_assert(std::is_class_v);\n\n // --- completion token variables ---\n asio::io_context surface_io;\n // detached — compile test for the constexpr variable and its usage\n asio::steady_timer t(surface_io, std::chrono::milliseconds(0));\n t.async_wait(asio::detached);\n static_assert(std::is_same_v);\n\n // use_future — accessible as a named variable\n auto uf = asio::use_future;\n (void)uf;\n\n // deferred\n static_assert(std::is_same_v);\n\n // --- redirect_error ---\n std::error_code redirect_ec;\n auto redirected = asio::redirect_error(redirect_ec);\n (void)redirected;\n\n // --- bind_executor ---\n auto bound = asio::bind_executor(asio::system_executor(), []{});\n (void)bound;\n\n // --- associated traits ---\n static_assert(std::is_class_v>);\n static_assert(std::is_class_v>);\n static_assert(std::is_class_v>);\n\n // --- error namespace ---\n if (asio::error::operation_aborted == std::error_code{}) return 10;\n\n return 0;\n}\n" + } + ] + }, + { + "project": "asio-ssl", + "text": "# chriskohlhoff.asio `ssl` FEATURE test — the end-to-end assertion for the\n# opt-in TLS surface: activating the feature compiles asio_ssl.cpp into the\n# package, pulls compat.openssl (3.5.1, built from source), and links\n# libssl.a + libcrypto.a. tests/ssl.cpp then runs a real TLS handshake over\n# loopback.\n#\n# Why this is its own member rather than a flag on tests/examples/asio-module:\n# features are resolved per consuming project, so one project cannot have asio\n# both with and without `ssl`; and the base member must keep asserting the\n# no-ssl build, which is what nearly every consumer takes.\n#\n# It needs BOTH `chriskohlhoff` and `compat`, and a project can only carry one\n# index repo — a second one pointing at the same tree makes every lookup\n# ambiguous (xlings#374). So the split is deliberate: this member declares NO\n# member-level [indices] and inherits the root's `compat`, which is the\n# namespace of the descriptor under test; asio itself is already published and\n# resolves from the remote index. The local asio descriptor, including this\n# feature's own wiring, is covered by tests/examples/asio-module.\n#\n# That inversion is the point — a feature dep resolves through whatever index\n# is configured for ITS namespace, so an unmerged compat descriptor can be\n# exercised through a published consumer that activates it.\n#\n# Linux, macOS, and Windows all resolve compat.openssl and execute the real\n# TLS handshake below. A selected member must always execute the real test.\n[package]\nname = \"asio-ssl-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.chriskohlhoff]\nasio = { version = \"1.38.1\", features = [\"ssl\"] }\n\n[target.'cfg(linux)'.build]\ncxxflags = [\"-DHAVE_ASIO_SSL=1\"]\n\n[target.'cfg(macos)'.dependencies.chriskohlhoff]\nasio = { version = \"1.38.1\", features = [\"ssl\"] }\n\n[target.'cfg(macos)'.build]\ncxxflags = [\"-DHAVE_ASIO_SSL=1\"]\n\n# Windows 也必须执行真实 TLS 测试;OpenSSL 的 Windows 构建由包描述负责。\n[target.'cfg(windows)'.dependencies.chriskohlhoff]\nasio = { version = \"1.38.1\", features = [\"ssl\"] }\n\n[target.'cfg(windows)'.build]\ncxxflags = [\"-DHAVE_ASIO_SSL=1\"]\n", + "files": [ + { + "path": "tests/examples/asio-ssl/tests/ssl.cpp", + "text": "// TLS echo test — ssl::stream wrapping a TCP socket, single-threaded\n// async handshake → write/read round-trip. PEM cert+key embedded as\n// string literals (use_certificate / use_private_key from const_buffer).\n//\n// This is the real end-to-end assertion for chriskohlhoff.asio's `ssl`\n// feature: the member activates it, which pulls in compat.openssl, compiles\n// asio_ssl.cpp, and links libssl.a/libcrypto.a.\n//\n// HAVE_ASIO_SSL is set by THIS project's own [target.'cfg(...)'.build]\n// cxxflags, not by the package's feature. A feature's `defines` apply to the\n// package's own translation units; a consumer that keys its source off one has\n// to declare it itself (same shape as the openblas member's HAVE_OPENBLAS).\n// HAVE_ASIO_SSL 由本测试成员自己的构建配置定义;所有已声明平台都必须运行真实 TLS 测试。\n#ifdef HAVE_ASIO_SSL\nimport std;\nimport asio;\n\nnamespace {\nconstexpr std::string_view cert_pem = R\"(-----BEGIN CERTIFICATE-----\nMIIDCzCCAfOgAwIBAgIUQzyqmufGtwwB//IMUo6WewD6UqgwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MDcyNjA4NTczNloYDzIxMjYw\nNzAyMDg1NzM2WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQCVXzA1Yve8b24Fjue5/A+ZPKFADt/4/945Vnm5hfEt\n2XOYGNaY9/M3lB/MSRRHB4lP8bxI0cj5AuHnlZkC7wiQQ3t/pdsWMQjmpfrLO9Aq\nWwgTA2Y94HNB8JtjUWLX/Kf+qLU8MQu374zrcnTyEYcrih0/mGMgvzmT5YXkB/La\n7CDyfV7l/vBfzAwtt3Q0YQpuS47ahraO+mnv0Y9wotnm7L8D5/uPUepbTtDNR9K2\nZ22SF5/WxElb/r6vRmmLOJbX29K8Z7hEtKswcJx9U40Rnt3j8mFvelEWLGTdFkj+\nn6KFX1eD95/0RzPWf9XrtkJ7zzJOPDj7Njp5zIfUjvuPAgMBAAGjUzBRMB0GA1Ud\nDgQWBBQkq8tq7Tnld/JWpfUJbFlBnRsFuDAfBgNVHSMEGDAWgBQkq8tq7Tnld/JW\npfUJbFlBnRsFuDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBu\n7Q+mh+Mf45QyuGfRMxu6q3RU4sjJAwRjrm6UwEmSBqUpKvOKnam4+jSFOLhz2uaz\nsk2WLs3zJ0SQb3uRzp92QNJmQmMf8TDl1sa32R41nCFs7CwLihyg3NwI1mWjN8Rm\nT+nfgea1o5K3BijDOoSCVzKUWv+K4OqZ679LxX4k02sMSKUd9kw1pMhZE2alxHoH\nMQW9IDkaCzjwvuYkzeFJdSNoxktf0ebWkNoZjAlTy83sDDiuwJ09OybnRql7Cifz\nSB+GQNhPRSdMH7cCv7uwokDiccDTK4VKrOsGC1G5y6Jd+C9L0PCngYi9vu1I8U90\nB+1vU0skeNeszdW++phe\n-----END CERTIFICATE-----\n)\";\n\nconstexpr std::string_view key_pem = R\"(-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCVXzA1Yve8b24F\njue5/A+ZPKFADt/4/945Vnm5hfEt2XOYGNaY9/M3lB/MSRRHB4lP8bxI0cj5AuHn\nlZkC7wiQQ3t/pdsWMQjmpfrLO9AqWwgTA2Y94HNB8JtjUWLX/Kf+qLU8MQu374zr\ncnTyEYcrih0/mGMgvzmT5YXkB/La7CDyfV7l/vBfzAwtt3Q0YQpuS47ahraO+mnv\n0Y9wotnm7L8D5/uPUepbTtDNR9K2Z22SF5/WxElb/r6vRmmLOJbX29K8Z7hEtKsw\ncJx9U40Rnt3j8mFvelEWLGTdFkj+n6KFX1eD95/0RzPWf9XrtkJ7zzJOPDj7Njp5\nzIfUjvuPAgMBAAECggEAL3nHutAv6WaJU57uLADffFb28YNI0L2ShirkGYFm/Kmm\nwerzGj+EwF+GE8oOdd0BWbV9oK987xhpcM/tiC8tS50HPbUbg1wmdhi/M6VZLn0s\nfc6Qyo3yVD0DRnfxsLCPPLOmlvEHxniPE66XWPEVQ1NspG/s4dWlmUpUWfvkxovW\nQ2nagMCwMjt/miadEQm64KznpN57yCYE8+ZAeWnWUoP/EtrTk9BVAC5s50vVJQKm\ngXW8t9g7mNE+6ywOOHajA2AbKFbk7Bi0vh/y4MkIiQATZW4KC98MU0e/JK8Y6cNb\nG94Xm5fA6MnYQdkpJ9AijD3sLwF5KRqiD4ro2+licQKBgQDGTnTyPEopnNK+/Ol0\n7i1kuqI9+WFkN/gwF01ZdzUzgqut5LHbSxUpf9RDYj7tMo7lsQHuGP1Om4fXLDN8\nJUTK0Gb0xlC43IgrNsXpziA4VnUhQ3FXiWTIUeKvAOpckww/wBfirTekUeReH8rN\nE8E7C+0Vz2Xp8BaIoJ4iDrVa4wKBgQDA1Ciy7fMyobJEfmBUxHvQo1GDModDwZb4\nXaaxH7hTuZd+tqAC1iUY8gzWvlO8rWm6+sSNwu1noMVEtsc0OzKBv5h6EB6NhBOd\nXNacKP7DSo4K6420KkFh5t76p2uKlN3ge2CIzTiESsYf+TP6QXaC/uh455OSe9NJ\nNZ1MGe9gZQKBgHUx6sU5wi6DgrziZOn41JTiA34Swm7i8Oci7lCANc3CXMmBDWdn\nIROMexpzlnLB3Vd7W4Ol+xWYrxgIBElLETO3JBFmnlAR7Nt1HFPHwJzq44AMBpDQ\nHuKQGiKIrPiW4rdORA9vhSG0T/0cVtMJ6LmHm8626ijt/bMzESFZhe43AoGAX3ls\ngVOBw8L94h30kmQKrf3/QQeGo8y5dFXiT/bVrFbLJMlFpsHi8lv+cWEhUt1F6Xd6\nVHp8U3/tzJz3OuxIkKeN1noetpD7qUGrXPyLT6Sdedixe9AkOVY3d0Hn5GDbDufn\nnzSFVDM1r+USkElTZX7TGfIHRlMbBTePn3uD42UCgYAHiAr2RB/JewvjpQkZCkWT\nwtEKliMPx8aOH24D5nUvtMIQlrCNmiy3SqqIaWyG2oMk4EsDmUQiCUj7Ov0+R5nC\nvEbmTFZL2J/YTXm5JR0jWCCz9oIjxYCp6z1CkW9IxBgyCVPnh4fe+/pgMvkskhRz\nVGJlxAgzCXYPd1NGSakFTg==\n-----END PRIVATE KEY-----\n)\";\n}\n\nint main() {\n using namespace std::chrono_literals;\n\n asio::io_context io;\n\n // --- SSL contexts (cert+key from memory) ---\n asio::ssl::context ssl_ctx(asio::ssl::context::tls_server);\n ssl_ctx.use_certificate(\n asio::buffer(cert_pem.data(), cert_pem.size()),\n asio::ssl::context_base::pem);\n ssl_ctx.use_private_key(\n asio::buffer(key_pem.data(), key_pem.size()),\n asio::ssl::context_base::pem);\n\n asio::ssl::context client_ctx(asio::ssl::context::tls_client);\n\n // --- TCP acceptor ---\n asio::ip::tcp::acceptor acceptor(io, {asio::ip::address_v4::loopback(), 0});\n asio::ip::tcp::socket server_sock(io);\n asio::ssl::stream server_stream(server_sock, ssl_ctx);\n\n // --- deadline ---\n asio::steady_timer deadline(io, 5s);\n int failure = 0;\n\n auto fail = [&](int code) {\n if (failure == 0) failure = code;\n std::error_code ignored;\n acceptor.close(ignored);\n deadline.cancel();\n };\n\n deadline.async_wait([&](const std::error_code& ec) { if (!ec) fail(90); });\n\n const std::string ping = \"tls-ping\";\n const std::string pong = \"tls-pong\";\n\n // --- server: accept → handshake → read → write ---\n acceptor.async_accept(server_sock, [&](const std::error_code& ec) {\n if (ec) return fail(1);\n server_stream.async_handshake(asio::ssl::stream_base::server,\n [&](const std::error_code& hsec) {\n if (hsec) return fail(2);\n static std::array data{};\n asio::async_read(server_stream, asio::buffer(data),\n [&](const std::error_code& rec, std::size_t n) {\n if (rec || n != ping.size()\n || std::string(data.data(), n) != ping) return fail(3);\n asio::async_write(server_stream, asio::buffer(pong),\n [&](const std::error_code& wec, std::size_t) {\n if (wec) fail(4);\n });\n });\n });\n });\n\n // --- client: connect → handshake → write → read ---\n bool client_done = false;\n asio::ssl::stream client_stream(io, client_ctx);\n client_stream.lowest_layer().connect(\n {asio::ip::address_v4::loopback(), acceptor.local_endpoint().port()});\n\n client_stream.async_handshake(asio::ssl::stream_base::client,\n [&](const std::error_code& ec) {\n if (ec) return fail(5);\n asio::async_write(client_stream, asio::buffer(ping),\n [&](const std::error_code& wec, std::size_t written) {\n if (wec || written != ping.size()) return fail(6);\n static std::array data{};\n asio::async_read(client_stream, asio::buffer(data),\n [&](const std::error_code& rec, std::size_t n) {\n if (rec || n != pong.size()\n || std::string(data.data(), n) != pong) return fail(7);\n client_done = true;\n deadline.cancel();\n });\n });\n });\n\n io.run();\n return failure ? failure : (client_done ? 0 : 8);\n}\n#else\n#error \"HAVE_ASIO_SSL must be enabled for every declared asio-ssl test platform\"\n#endif\n" + } + ] + }, + { + "project": "boost-any", + "text": "# compat.boost-any test project.\n[package]\nname = \"boost-any-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-any = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-any/tests/any.cpp", + "text": "#include \n\n#include \n\nint main() {\n boost::any value = 42;\n if (value.empty()) return 1;\n if (boost::any_cast(value) != 42) return 1;\n\n value = std::string(\"muduo\");\n const auto* text = boost::any_cast(&value);\n if (text == nullptr || *text != \"muduo\") return 1;\n\n value.clear();\n return value.empty() ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-asio", + "text": "# compat.boost-asio test project: the direct-consumer path for the boostorg\n# asio tree — feature-define propagation (BOOST_ASIO_DISABLE_BOOST_* reach\n# this TU), a real steady_timer round through io_context, and the coroutine\n# surface (co_spawn + awaitable). Stackful spawn/deadline_timer stay absent\n# by design; the descriptor's disable defines are what keep boost.context and\n# boost.date_time out of the closure.\n[package]\nname = \"boost-asio-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-asio = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-asio/tests/asio.cpp", + "text": "// Behavioral test for compat.boost-asio: a real steady_timer fires through a\n// running io_context, post/defech queue in order, and the C++20 coroutine\n// surface (co_spawn + awaitable) adds numbers. All offline, no sockets.\n#include \n\nint main() {\n namespace asio = boost::asio;\n using asio::ip::tcp;\n bool ok = true;\n\n // 1. A steady_timer with a 1ms expiry actually wakes the io_context.\n {\n asio::io_context ctx;\n bool fired = false;\n asio::steady_timer timer(ctx, std::chrono::milliseconds(1));\n timer.async_wait([&](const boost::system::error_code& ec) {\n fired = !ec.failed();\n });\n ctx.run();\n ok = ok && fired;\n }\n\n // 2. post and defer both run; a strand-wrapped counter is consistent.\n {\n asio::io_context ctx;\n int counter = 0;\n asio::post(ctx, [&] { ++counter; });\n asio::defer(ctx, [&] { ++counter; });\n ctx.run();\n ok = ok && counter == 2;\n }\n\n // 3. co_spawn + awaitable: the coroutine surface works end to end.\n {\n asio::io_context ctx;\n auto adder = [](int a, int b) -> asio::awaitable { co_return a + b; };\n int result = 0;\n asio::co_spawn(\n ctx,\n [&]() -> asio::awaitable {\n result = co_await adder(20, 22);\n },\n asio::detached);\n ctx.run();\n ok = ok && result == 42;\n }\n\n // 4. The tcp endpoint/address plumbing compiles and resolves offline.\n {\n tcp::endpoint ep(asio::ip::make_address(\"127.0.0.1\"), 8080);\n ok = ok && ep.address().is_loopback() && ep.port() == 8080;\n }\n\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-assert", + "text": "# compat.boost-assert test project: asserts the assert/current_function/\n# source_location surface compiles and behaves at runtime.\n[package]\nname = \"boost-assert-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-assert = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-assert/tests/assert.cpp", + "text": "// Behavioral test: BOOST_VERIFY's expression is evaluated in ALL build modes\n// (unlike BOOST_ASSERT, which compiles away under NDEBUG), and the\n// source_location / current_function utilities return live data.\n#include \n#include \n#include \n\nstatic int answer() { return 42; }\n\nint main() {\n bool ran = false;\n BOOST_VERIFY(answer() == 42 && (ran = true)); // must run even under NDEBUG\n\n const char* fn = BOOST_CURRENT_FUNCTION;\n bool fn_ok = fn != nullptr && *fn != '\\0';\n\n constexpr boost::source_location loc = BOOST_CURRENT_LOCATION;\n bool loc_ok = loc.file_name() != nullptr && loc.line() > 0;\n\n return (ran && fn_ok && loc_ok) ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-beast", + "text": "# compat.boost-beast test project: offline HTTP parse/serialize round-trip,\n# zlib round-trip and SSL-detection — the assertions exercise the whole\n# 25-package modular-boost closure transitively (asio, system, core, mp11,\n# intrusive, ...) through one `#include `.\n[package]\nname = \"boost-beast-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-beast = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-beast/tests/beast.cpp", + "text": "// Behavioral test for compat.boost-beast, entirely offline: buffer types,\n// HTTP request parsing, response serialize + parse round-trip, the error\n// path on malformed input, the bundled zlib streams, and beast::static_string.\n// Including the boost/beast.hpp aggregate forces one TU through core + http +\n// websocket + zlib, which is what validates the whole modular-boost closure\n// wiring (asio, system, mp11, intrusive, ...) against the 1.92.0 train.\n#include \n\n#include \n\nint main() {\n namespace beast = boost::beast;\n namespace asio = boost::asio;\n namespace http = beast::http;\n namespace zlib = beast::zlib;\n bool ok = true;\n\n // beast's parser.put() returns the number of octets it parsed and the\n // caller must consume exactly that many from the dynamic buffer before\n // feeding the rest — the same contract http::read() implements on top.\n auto parse_all = [](auto& parser, beast::flat_buffer& buf) {\n beast::error_code ec;\n while (!parser.is_done()) {\n auto const consumed = parser.put(buf.data(), ec);\n if (ec.failed() || consumed == 0) break;\n buf.consume(consumed);\n }\n return ec;\n };\n\n // 1. multi_buffer (an intrusive list of extents) holds appended chunks;\n // flat_buffer linearizes them byte-for-byte.\n {\n beast::multi_buffer mb;\n asio::const_buffer chunk(\"0123456789\", 10);\n mb.commit(asio::buffer_copy(mb.prepare(10), chunk));\n mb.commit(asio::buffer_copy(mb.prepare(10), chunk));\n ok = ok && mb.size() == 20;\n\n beast::flat_buffer fb;\n fb.commit(asio::buffer_copy(fb.prepare(mb.size()), mb.data()));\n ok = ok && beast::buffers_to_string(fb.data()) ==\n \"01234567890123456789\";\n }\n\n // 2\\. Parse a request carrying headers and a body; assert the fields.\n {\n std::string const wire =\n \"PUT /things/42 HTTP/1.1\\r\\n\"\n \"Host: example.com\\r\\n\"\n \"Content-Type: text/plain\\r\\n\"\n \"Content-Length: 5\\r\\n\"\n \"\\r\\n\"\n \"hello\";\n beast::flat_buffer buf;\n buf.commit(asio::buffer_copy(buf.prepare(wire.size()),\n asio::buffer(wire)));\n\n http::request_parser parser;\n beast::error_code ec = parse_all(parser, buf);\n ok = ok && !ec.failed() && parser.is_done();\n\n auto const& req = parser.get();\n ok = ok && req.method() == http::verb::put;\n ok = ok && req.target() == \"/things/42\";\n ok = ok && req.version() == 11;\n ok = ok && req[\"Host\"] == \"example.com\";\n ok = ok && req[\"Content-Type\"] == \"text/plain\";\n ok = ok && req.body() == \"hello\";\n }\n\n // 3\\. Serialize a response, parse the wire bytes back; values survive.\n {\n http::response res{http::status::created, 11};\n res.set(http::field::content_type, \"application/json\");\n res.body() = \"{\\\"ok\\\":true}\";\n res.prepare_payload();\n\n http::response_serializer sr(res);\n std::string out;\n beast::error_code sec;\n while (!sr.is_done()) {\n sr.next(sec, [&](beast::error_code const&,\n auto const& bytes) {\n out += beast::buffers_to_string(bytes);\n sr.consume(beast::buffer_bytes(bytes));\n });\n }\n ok = ok && !sec.failed();\n ok = ok && out.find(\"HTTP/1.1 201 Created\\r\\n\") == 0;\n ok = ok &&\n out.find(\"Content-Type: application/json\") != std::string::npos;\n ok = ok && out.find(\"{\\\"ok\\\":true}\") != std::string::npos;\n\n beast::flat_buffer buf;\n buf.commit(asio::buffer_copy(buf.prepare(out.size()),\n asio::buffer(out)));\n http::response_parser parser;\n beast::error_code ec = parse_all(parser, buf);\n ok = ok && !ec.failed() && parser.is_done();\n\n auto const& back = parser.get();\n ok = ok && back.result() == http::status::created;\n ok = ok && back.version() == 11;\n ok = ok && back[http::field::content_type] == \"application/json\";\n ok = ok && back.body() == \"{\\\"ok\\\":true}\";\n }\n\n // 4\\. Malformed input must fail the parser through beast's error type.\n {\n std::string const bad = \"NONSENSE\\r\\n\\r\\n\";\n beast::flat_buffer buf;\n buf.commit(asio::buffer_copy(buf.prepare(bad.size()),\n asio::buffer(bad)));\n http::request_parser parser;\n beast::error_code ec = parse_all(parser, buf);\n ok = ok && ec.failed();\n }\n\n // 5\\. The bundled zlib implementation (no external libz) round-trips data\n // that compresses well.\n {\n std::string const text(8192, 'a');\n beast::error_code ec;\n\n zlib::deflate_stream def;\n def.reset(6, 15, 8, zlib::Strategy::normal);\n std::string comp(1024, '\\0');\n zlib::z_params zd{};\n zd.next_in = text.data();\n zd.avail_in = text.size();\n zd.next_out = comp.data();\n zd.avail_out = comp.size();\n def.write(zd, zlib::Flush::full, ec);\n ok = ok && !ec.failed();\n comp.resize(comp.size() - zd.avail_out);\n ok = ok && !comp.empty() && comp.size() < text.size();\n\n zlib::inflate_stream inf;\n inf.reset(15);\n std::string back(text.size() + 64, '\\0');\n zlib::z_params zi{};\n zi.next_in = comp.data();\n zi.avail_in = comp.size();\n zi.next_out = back.data();\n zi.avail_out = back.size();\n inf.write(zi, zlib::Flush::none, ec);\n ok = ok && !ec.failed();\n back.resize(back.size() - zi.avail_out);\n ok = ok && back == text;\n }\n\n // 6\\. beast::static_string (boost/static_string) stays within capacity and\n // compares like a string.\n {\n beast::static_string<16> s{\"hello\"};\n s.append(\", world\");\n ok = ok && s.size() == 12;\n ok = ok && s == \"hello, world\";\n }\n\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-circular-buffer", + "text": "# compat.boost-circular-buffer test project.\n[package]\nname = \"boost-circular-buffer-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-circular-buffer = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-circular-buffer/tests/circular_buffer.cpp", + "text": "#include \n\nint main() {\n boost::circular_buffer values(3);\n values.push_back(1);\n values.push_back(2);\n values.push_back(3);\n values.push_back(4);\n return values.size() == 3 && values.front() == 2 && values.back() == 4\n ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-concept-check", + "text": "# compat.boost-concept-check test project.\n[package]\nname = \"boost-concept-check-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-concept-check = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-concept-check/tests/concepts.cpp", + "text": "#include \n\nBOOST_CONCEPT_ASSERT((boost::CopyConstructible));\nBOOST_CONCEPT_ASSERT((boost::DefaultConstructible));\n\nint main() {\n int value = 42;\n return value == 42 ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-config", + "text": "# compat.boost-config test project: the root of the modular-boost header\n# family this index carries. Asserts the pinned 1.92.0 train and positive\n# platform detection under `mcpp test`. Resolves via the inherited\n# workspace-root `[indices]` compat redirect.\n[package]\nname = \"boost-config-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-config = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-config/tests/config.cpp", + "text": "// Behavioral test: the compat.boost-config headers are present and identify\n// themselves as exactly the pinned 1.92.0 train. If the descriptor ever\n// drifts to another release without this member following, this fails loudly.\n#include \n#include \n#include \n\nint main() {\n // BOOST_VERSION encodes major*100000 + minor*100 + patch -> 1.92.0.\n bool ok = BOOST_VERSION == 109200;\n ok = ok && std::string_view(BOOST_LIB_VERSION) == \"1_92\";\n\n // Platform detection must have positively selected THIS platform's header:\n // each platform/*.hpp stamps BOOST_PLATFORM with its own string. A config\n // that silently defaulted everywhere would fail on every branch.\n#if defined(_WIN32)\n ok = ok && std::string_view(BOOST_PLATFORM) == \"Win32\";\n#elif defined(__APPLE__) && defined(__MACH__)\n ok = ok && std::string_view(BOOST_PLATFORM) == \"Mac OS\";\n#elif defined(__linux__)\n ok = ok && std::string_view(BOOST_PLATFORM) == \"linux\";\n#else\n ok = false; // not a platform this index claims\n#endif\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-ext.ut", + "text": "# boost-ext.ut test project: consumes the C++23 module `boost.ut` and\n# asserts two test styles (UDL + function syntax) plus an expect failure\n# under `mcpp test`.\n# Overrides the workspace-root redirect (`compat`): this member needs the\n# `boost-ext` namespace. A member-level [indices] REPLACES the inherited\n# table rather than merging with it — keeps the project index repo to ONE.\n[indices]\nboost-ext = { path = \"../../..\" }\n\n[package]\nname = \"boost-ext-ut-tests\"\nversion = \"0.1.0\"\n\n[dependencies.boost-ext]\nut = \"2.3.1\"\n", + "files": [ + { + "path": "tests/examples/boost-ext.ut/tests/ut.cpp", + "text": "// Behavioral test: build two tests under [Boost::ext].UT via `import boost.ut;`,\n// then exercise the UDL (\"...\"_test) and function-call (test(\"...\")) syntaxes.\n// Counts test registration by reading ut's `tests_runner` directly is awkward,\n// so the test instead just RUNS a passing assertion in each style; if the\n// module fails to import or any expect() fails, [Boost::ext].UT reports the\n// failure through its test runner and `mcpp test` returns non-zero.\nimport std;\nimport boost.ut;\n\nusing namespace boost::ut;\n\nint main() {\n \"UDL syntax\"_test = [] {\n expect(42_i == 42);\n };\n\n test(\"function syntax\") = [] {\n expect(0_i == 0);\n expect(1u == 1_u);\n };\n\n return 0;\n}\n" + } + ] + }, + { + "project": "boost-family", + "text": "# compat boost 家族\"大测试\"成员:为每个没有专属成员的小依赖包提供行为断言。\n# 消费全部 23 个小包(boost-beast / boost-asio / 既有 uuid/config/assert/\n# throw-exception/type-traits 各有专属成员,不在其中),按主题分成 6 个\n# tests/*.cpp,每个组件都有可失败的有效断言。\n[package]\nname = \"boost-family-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-align = \"1.92.0\"\nboost-bind = \"1.92.0\"\nboost-compat = \"1.92.0\"\nboost-container = \"1.92.0\"\nboost-container-hash = \"1.92.0\"\nboost-core = \"1.92.0\"\nboost-describe = \"1.92.0\"\nboost-endian = \"1.92.0\"\nboost-intrusive = \"1.92.0\"\nboost-io = \"1.92.0\"\nboost-logic = \"1.92.0\"\nboost-move = \"1.92.0\"\nboost-mp11 = \"1.92.0\"\nboost-optional = \"1.92.0\"\nboost-predef = \"1.92.0\"\nboost-preprocessor = \"1.92.0\"\nboost-smart-ptr = \"1.92.0\"\nboost-static-string = \"1.92.0\"\nboost-system = \"1.92.0\"\nboost-type-index = \"1.92.0\"\nboost-utility = \"1.92.0\"\nboost-variant2 = \"1.92.0\"\nboost-winapi = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-family/tests/bytes-logic.cpp", + "text": "// boost-endian (byte-order conversion), boost-container-hash (hashing),\n// boost-logic (tribool) and boost-align (pointer alignment helpers).\n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n\nint main() {\n bool ok = true;\n\n // endian: reversing is an involution with byte-exact results.\n {\n std::uint32_t x = 0x01020304u;\n ok = ok && boost::endian::endian_reverse(x) == 0x04030201u;\n ok = ok &&\n boost::endian::endian_reverse(boost::endian::endian_reverse(x)) == x;\n }\n\n // container-hash: deterministic within a process; combining is stable.\n {\n boost::hash hs;\n ok = ok && hs(\"abc\") == hs(\"abc\");\n\n const std::vector v1{1, 2, 3}, v2{1, 2, 3};\n std::size_t seed = 0;\n boost::hash_range(seed, v1.begin(), v1.end());\n std::size_t seed2 = 0;\n boost::hash_range(seed2, v2.begin(), v2.end());\n ok = ok && seed == seed2 && seed != 0;\n }\n\n // logic: three-valued truth table entry points used by beast::detect_ssl.\n // Every operator (including !) returns tribool; the explicit conversion\n // and the indeterminate() free function are the way back to bool.\n {\n using boost::logic::indeterminate;\n using boost::logic::tribool;\n tribool t = true, f = false, i = indeterminate;\n\n ok = ok && static_cast(t) && !static_cast(f);\n ok = ok && indeterminate(i);\n ok = ok && indeterminate(t && i);\n ok = ok && static_cast(t || i); // true dominates OR\n ok = ok && !static_cast(f && i); // false dominates AND\n ok = ok && indeterminate(i || f) && indeterminate(i && t);\n }\n\n // align: round-tripping through align_up lands on the boundary and\n // align_down undoes it.\n {\n alignas(64) char raw[128];\n ok = ok && boost::alignment::is_aligned(boost::alignment::align_up(raw, 64), 64);\n ok = ok && boost::alignment::align_down(\n boost::alignment::align_up(raw, 64), 64) ==\n boost::alignment::align_up(raw, 64);\n }\n\n return ok ? 0 : 1;\n}\n" + }, + { + "path": "tests/examples/boost-family/tests/containers.cpp", + "text": "// boost-container (vector), boost-intrusive (list), boost-optional\n// and boost-static-string.\n#include \n#include \n#include \n#include \n\nint main() {\n bool ok = true;\n\n // container: an STL-compatible vector that is not std::vector.\n {\n boost::container::vector vec;\n for (int i = 0; i < 100; ++i) vec.push_back(i);\n ok = ok && vec.size() == 100 && vec.back() == 99 && vec[42] == 42;\n }\n\n // intrusive: the list only links; ownership stays with the nodes.\n {\n namespace intr = boost::intrusive;\n struct Node : intr::list_base_hook<> {\n int v;\n explicit Node(int x) : v(x) {}\n };\n intr::list lst;\n Node a(1), b(2), c(3);\n lst.push_back(a);\n lst.push_back(b);\n lst.push_back(c);\n ok = ok && lst.size() == 3 && lst.front().v == 1 && lst.back().v == 3;\n\n lst.pop_front();\n ok = ok && lst.front().v == 2 && a.v == 1; // a outlives its unlinking\n\n lst.clear(); // unlink everything: hooks must not die while linked\n ok = ok && lst.empty();\n }\n\n // optional: empty/engaged lifecycle and value_or fallback.\n {\n boost::optional o;\n ok = ok && !o && o.value_or(-1) == -1;\n\n o = 9;\n ok = ok && o && *o == 9 && o.value() == 9;\n\n o.reset();\n ok = ok && !o;\n }\n\n // static-string: fixed-capacity string with a compile-time bound.\n {\n boost::static_strings::static_string<8> s(\"abc\");\n s += \"def\";\n ok = ok && s == \"abcdef\" && s.size() == 6;\n ok = ok && s.compare(0, 3, \"abc\") == 0;\n }\n\n return ok ? 0 : 1;\n}\n" + }, + { + "path": "tests/examples/boost-family/tests/core-smart.cpp", + "text": "// boost-core (addressof / empty_value / ref), boost-smart-ptr\n// (shared_ptr/weak_ptr/make_shared) and boost-move (movelib::unique_ptr).\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nint main() {\n bool ok = true;\n\n // core: addressof survives overloaded operator&, ref wraps without copies.\n {\n struct Tricky {\n int v = 7;\n Tricky* operator&() const { return nullptr; }\n } tricky;\n ok = ok && boost::addressof(tricky) != nullptr;\n ok = ok && boost::addressof(tricky)->v == 7;\n\n auto r = boost::cref(tricky.v);\n ok = ok && r.get() == 7;\n }\n\n // core: empty_value with a non-empty payload stores and returns it.\n {\n boost::empty_value ev(boost::empty_init_t(), 42);\n ok = ok && ev.get() == 42;\n }\n\n // smart-ptr: reference counting across shared/weak.\n {\n boost::shared_ptr sp = boost::make_shared(5);\n boost::weak_ptr wp = sp;\n ok = ok && sp.use_count() == 1 && *sp == 5 && !wp.expired();\n\n sp.reset();\n ok = ok && wp.expired() && wp.lock() == boost::shared_ptr();\n }\n\n // move: movelib::unique_ptr transfers sole ownership.\n {\n boost::movelib::unique_ptr up = boost::movelib::make_unique(11);\n boost::movelib::unique_ptr up2 = boost::move(up);\n ok = ok && !up && up2 && *up2 == 11;\n }\n\n return ok ? 0 : 1;\n}\n" + }, + { + "path": "tests/examples/boost-family/tests/io-platform.cpp", + "text": "// boost-io (stream state savers), boost-utility (compressed_pair) and\n// boost-winapi (WinAPI type shims — compilable only where Win32 exists).\n#include \n#include \n\n#include \n#include \n#include \n\n#ifdef _WIN32\n#include \n#endif\n\nint main() {\n bool ok = true;\n\n // io: RAII flag saver restores the stream's formatting state.\n {\n std::ostringstream os;\n os << 255;\n ok = ok && os.str() == \"255\";\n\n os.str(\"\");\n {\n boost::io::ios_flags_saver fs(os);\n os << std::hex << 255;\n ok = ok && os.str() == \"ff\";\n }\n\n os.str(\"\");\n os << 255;\n ok = ok && os.str() == \"255\"; // hex did not leak\n }\n\n // utility: compressed_pair — empty-base-optimized pair with accessors.\n {\n boost::compressed_pair cp(3, \"three\");\n ok = ok && cp.first() == 3 && cp.second() == \"three\";\n\n // Both members empty: compiles and stays stateless in practice.\n struct Nothing {};\n boost::compressed_pair empty_pair{};\n ok = ok && sizeof(empty_pair) <= sizeof(Nothing) * 2;\n }\n\n // winapi: the shims declare the Win32 surface themselves, but the\n // headers are Windows-only to compile (they #error elsewhere), so real\n // assertions run under the Windows CI leg only.\n {\n#ifdef _WIN32\n ok = ok && std::is_pointer::value;\n ok = ok && std::is_integral::value;\n ok = ok && sizeof(boost::winapi::DWORD_) >= 4;\n#endif\n }\n\n return ok ? 0 : 1;\n}\n" + }, + { + "path": "tests/examples/boost-family/tests/meta.cpp", + "text": "// boost-mp11 (list metaprogramming), boost-describe (enum reflection),\n// boost-preprocessor (token repetition), boost-type-index (runtime type\n// identity) and boost-predef (platform/compiler detection).\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n\n// Enumerators must be visible at global scope for the describe macros.\nenum class Color { red = 1, green = 2, blue = 4 };\nBOOST_DESCRIBE_ENUM(Color, red, green, blue)\n\n#define FAMILY_INC(z, n, d) + (n)\n\nint main() {\n bool ok = true;\n\n // mp11: list size and per-type iteration.\n {\n using L = boost::mp11::mp_list;\n ok = ok && boost::mp11::mp_size::value == 3;\n\n int total = 0;\n boost::mp11::mp_for_each([&](auto t) { total += sizeof(decltype(t)); });\n ok = ok && total > 0 && total >= 3;\n }\n\n // describe: enumerate (name, value) pairs without RTTI tricks.\n {\n int count = 0;\n bool blueNamed = false;\n boost::mp11::mp_for_each>(\n [&](auto d) {\n ++count;\n if (d.value == Color::blue)\n blueNamed = std::string(d.name) == \"blue\";\n });\n ok = ok && count == 3 && blueNamed;\n }\n\n // preprocessor: stringize/cat composition and unrolled repetition.\n {\n ok = ok && std::string(BOOST_PP_STRINGIZE(BOOST_PP_CAT(fo, o))) == \"foo\";\n\n int total = BOOST_PP_REPEAT(5, FAMILY_INC, 0); // + 0 + 1 + 2 + 3 + 4\n ok = ok && total == 10;\n }\n\n // type-index: runtime type identity with a readable name.\n {\n namespace ti = boost::typeindex;\n ok = ok && ti::type_id() == ti::type_id();\n ok = ok && ti::type_id() != ti::type_id();\n ok = ok && std::string(ti::type_id().pretty_name()).find(\"int\") !=\n std::string::npos;\n }\n\n // predef: the platform and compiler we run the index's CI on must be\n // detected (predef defines every macro, 0 when not detected).\n {\n ok = ok && (BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_WINDOWS) != 0;\n ok = ok && (BOOST_COMP_CLANG || BOOST_COMP_GNUC || BOOST_COMP_MSVC) != 0;\n }\n\n return ok ? 0 : 1;\n}\n" + }, + { + "path": "tests/examples/boost-family/tests/system-errors.cpp", + "text": "// boost-system (error_code/system_error), boost-compat (invoke backport)\n// and boost-bind (bind/mem_fn with placeholders).\n#include \n#include \n#include \n#include \n\n#include \n\nint family_sub(int a, int b) { return a - b; }\n\nstruct Point {\n int x = 31;\n int get() const { return x; }\n};\n\nint main() {\n bool ok = true;\n\n // system: generic errc mapping, category identity, throwing with a code.\n {\n namespace sys = boost::system;\n sys::error_code ec = sys::errc::make_error_code(sys::errc::invalid_argument);\n ok = ok && static_cast(ec);\n ok = ok && ec == sys::errc::invalid_argument;\n ok = ok && ec.category() == sys::generic_category();\n\n bool caught = false;\n try {\n throw sys::system_error(ec);\n } catch (const sys::system_error& e) {\n caught = e.code() == sys::errc::invalid_argument;\n }\n ok = ok && caught;\n }\n\n // compat: C++11-17 std::invoke backport, including member-object access.\n {\n struct Inc {\n int operator()(int x) const { return x + 1; }\n };\n ok = ok && boost::compat::invoke(Inc{}, 41) == 42;\n ok = ok && boost::compat::invoke([](int a, int b) { return a * b; }, 6, 7) == 42;\n\n Point p;\n ok = ok && boost::compat::invoke(&Point::x, p) == 31;\n ok = ok && boost::compat::invoke(&Point::get, p) == 31;\n }\n\n // bind: partial application through placeholders, plus mem_fn.\n {\n namespace ph = boost::placeholders;\n auto f = boost::bind(family_sub, ph::_1, 3);\n ok = ok && f(10) == 7;\n\n Point p;\n auto byName = boost::bind(&Point::x, ph::_1);\n ok = ok && byName(p) == 31;\n\n auto m = boost::mem_fn(&Point::get);\n ok = ok && m(p) == 31;\n }\n\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-module", + "text": "# boost.boost C++23-module package test: selective feature consumption of the\n# Boost named-modules wrapper — `import boost.optional;` plus the umbrella\n# `import boost;`. The package is Form A (upstream ships its own mcpp.toml), so\n# this member is also the only place the descriptor's `mcpp = \"*/mcpp.toml\"`\n# default lookup is exercised.\n#\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `ZheFeng7110`. The table is keyed by NAMESPACE, so the key must be the\n# dependency's namespace (`ZheFeng7110`), not the package name (`boost`). A\n# member-level [indices] REPLACES the inherited table rather than merging with\n# it, which keeps this to ONE project index repo — two repos pointing at the\n# same tree make every lookup ambiguous.\n[indices]\nZheFeng7110 = { path = \"../../..\" }\n\n[package]\nname = \"boost-module-tests\"\nversion = \"0.1.0\"\n\n# Selective, NOT the package's 49-library default closure: opt out of the\n# default set and pick a core sample (header-only optional/type_traits, the\n# compiled json, and the version constants). This keeps index CI on the\n# validated module surface without building the whole wrapper.\n[dependencies.ZheFeng7110]\nboost = { version = \"1.91.0.0.1.0\", default-features = false, features = [\"optional\", \"type_traits\", \"json\", \"version\"] }\n", + "files": [ + { + "path": "tests/examples/boost-module/tests/boost_module.cpp", + "text": "// Public boost.boost module-package end-to-end assertion: consume the Boost\n// named-modules wrapper by import only (no `#include ` at all).\n//\n// * `import boost.optional;` — a classic header-only library\n// * `import boost.json;` — a compiled library (library TU linked in)\n// * `import boost.version;` — the version constants module\n// * `import boost;` — the umbrella, re-exporting the active set\n\nimport std;\n\nimport boost.optional;\nimport boost.json;\nimport boost.version;\nimport boost;\n\nint main() {\n int failures = 0;\n auto check = [&failures](bool ok, const char* what) {\n if (!ok) {\n std::println(\"FAIL: {}\", what);\n ++failures;\n }\n };\n\n // optional (header-only module)\n boost::optional empty;\n check(!empty.has_value(), \"optional: empty has no value\");\n boost::optional v(42);\n check(v.has_value() && *v == 42, \"optional: value round-trip\");\n check(v.value_or(0) == 42, \"optional: value_or hits the value\");\n empty = 7;\n check(empty.value_or(0) == 7, \"optional: assignment activates\");\n\n // json (compiled library)\n boost::json::value j = boost::json::parse(R\"({\"lib\":\"boost\",\"ok\":true})\");\n check(j.is_object(), \"json: parsed to an object\");\n boost::json::object& o = j.as_object();\n check(o[\"lib\"].as_string() == \"boost\", \"json: string member\");\n check(o[\"ok\"].as_bool(), \"json: bool member\");\n const std::string ser = boost::json::serialize(j);\n check(boost::json::parse(ser).as_object()[\"lib\"].as_string() == \"boost\",\n \"json: serialize/parse round-trip\");\n\n // version constants\n check(boost::BOOST_VERSION == 109100, \"version: BOOST_VERSION == 109100\");\n check(boost::BOOST_LIB_VERSION[0] == '1', \"version: BOOST_LIB_VERSION\");\n\n // type_traits (reached through the umbrella's active set as well)\n check(boost::is_same::value, \"type_traits: is_same\");\n\n if (failures == 0) {\n std::println(\"boost.boost modules ok: BOOST_VERSION={}\", boost::BOOST_VERSION);\n }\n return failures == 0 ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-throw-exception", + "text": "# compat.boost-throw-exception test project: BOOST_THROW_EXCEPTION raises a\n# std::exception-derived error carrying the original message.\n[package]\nname = \"boost-throw-exception-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-throw-exception = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-throw-exception/tests/throw_exception.cpp", + "text": "// Behavioral test: BOOST_THROW_EXCEPTION raises an exception catchable as its\n// own std::exception base, carrying the original message through what().\n#include \n#include \n#include \n\nint main() {\n bool caught = false;\n try {\n BOOST_THROW_EXCEPTION(std::runtime_error(\"compat probe\"));\n } catch (const std::exception& e) {\n caught = std::strstr(e.what(), \"compat probe\") != nullptr;\n }\n return caught ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-type-traits", + "text": "# compat.boost-type-traits test project: the trait answers are asserted both\n# statically (compile fails on wrong answers) and through a runtime value.\n[package]\nname = \"boost-type-traits-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-type-traits = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-type-traits/tests/type_traits.cpp", + "text": "// Behavioral test: core traits give the right answers, cross-checked against\n// the standard library where an equivalent exists.\n#include \n#include \n#include \n#include \n\nint main() {\n static_assert(boost::is_same::value, \"same type must match\");\n static_assert(!boost::is_same::value, \"distinct types must differ\");\n static_assert(std::is_same::type, int*>::value,\n \"add_pointer must yield int*\");\n static_assert(std::is_same::type, int>::value,\n \"remove_cv must strip const/volatile\");\n\n bool ok = boost::is_same::value; // runtime-visible path\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "boost-uuid", + "text": "# compat.boost-uuid test project: RFC 4122 shape, string round-trip,\n# deterministic name generation and std::hash. Pulls the whole modular-boost\n# header family transitively through the descriptor's own deps.\n[package]\nname = \"boost-uuid-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nboost-uuid = \"1.92.0\"\n", + "files": [ + { + "path": "tests/examples/boost-uuid/tests/uuid.cpp", + "text": "// Behavioral test: RFC 4122 shape of generated UUIDs, string round-trip,\n// deterministic name generation, and the std::hash specialization.\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nint main() {\n namespace uuids = boost::uuids;\n bool ok = true;\n\n // Nil UUID round-trips through the canonical textual form.\n std::ostringstream nil_out;\n nil_out << uuids::nil_uuid();\n ok = ok && nil_out.str() == \"00000000-0000-0000-0000-000000000000\";\n\n // Parse -> print reproduces the canonical spelling byte for byte.\n uuids::string_generator parse;\n const uuids::uuid fixed =\n parse(\"f81d4fae-7dec-11d0-a765-00a0c91e6bf6\");\n std::ostringstream fixed_out;\n fixed_out << fixed;\n ok = ok && fixed_out.str() == \"f81d4fae-7dec-11d0-a765-00a0c91e6bf6\";\n\n // Random generator emits RFC 4122 version 4 / variant 1 shapes, and two\n // draws differ (collision odds are negligible by construction).\n uuids::random_generator rand;\n const uuids::uuid a = rand();\n const uuids::uuid b = rand();\n ok = ok && ((a.data[6] >> 4) == 4) && ((a.data[8] >> 6) == 0b10);\n ok = ok && a != b;\n\n // Name generator is deterministic per input and sensitive across inputs.\n uuids::name_generator_sha1 namegen(uuids::ns::dns());\n ok = ok && namegen(\"example.com\") == namegen(\"example.com\");\n ok = ok && namegen(\"example.com\") != namegen(\"other.example.com\");\n\n // The std::hash specialization agrees on equal values.\n ok = ok && std::hash{}(fixed) == std::hash{}(fixed);\n\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "brotli", + "text": "# Brotli test project: consumes compat.brotli through the workspace-root\n# `compat` index redirect and exercises encoder, decoder, and common code.\n[package]\nname = \"brotli-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nbrotli = \"1.2.0\"\n", + "files": [ + { + "path": "tests/examples/brotli/tests/roundtrip.cpp", + "text": "#include \n#include \n\n#include \n#include \n#include \n\nint main() {\n static constexpr unsigned char original[] =\n \"Brotli round-trip payload. Brotli round-trip payload. \"\n \"Brotli round-trip payload. Brotli round-trip payload.\";\n constexpr std::size_t original_size = sizeof(original) - 1;\n\n const std::size_t encoded_capacity = BrotliEncoderMaxCompressedSize(original_size);\n if (original_size == 0 || encoded_capacity == 0) return 1;\n\n std::vector encoded(encoded_capacity);\n std::size_t encoded_size = encoded.size();\n const BROTLI_BOOL encoded_ok = BrotliEncoderCompress(\n BROTLI_DEFAULT_QUALITY,\n BROTLI_DEFAULT_WINDOW,\n BROTLI_MODE_GENERIC,\n original_size,\n original,\n &encoded_size,\n encoded.data());\n if (encoded_ok != BROTLI_TRUE || encoded_size == 0) return 2;\n\n std::vector decoded(original_size);\n std::size_t decoded_size = decoded.size();\n const BrotliDecoderResult decoded_result = BrotliDecoderDecompress(\n encoded_size,\n encoded.data(),\n &decoded_size,\n decoded.data());\n if (decoded_result != BROTLI_DECODER_RESULT_SUCCESS) return 3;\n if (decoded_size != original_size) return 4;\n\n for (std::size_t i = 0; i < original_size; ++i) {\n if (decoded[i] != original[i]) return 5;\n }\n return std::memcmp(decoded.data(), original, original_size) == 0 ? 0 : 6;\n}\n" + } + ] + }, + { + "project": "build-mcpp", + "text": "# L3 build.mcpp consumer: the project-local build.mcpp generates a source and\n# emits a compile define; the test asserts both took effect. Exercises the\n# build.mcpp pipeline itself (no registry dependency).\n[package]\nname = \"build-mcpp-tests\"\nversion = \"0.1.0\"\n", + "files": [ + { + "path": "tests/examples/build-mcpp/tests/answer.cpp", + "text": "// Asserts both outputs of build.mcpp reached the build: the -D define (compile\n// flag) and the generated answer() function (a generated source linked in).\n#ifndef BUILT_BY_BUILD_MCPP\n#error \"build.mcpp cxxflag did not reach the test translation unit\"\n#endif\nint answer(); // from the build.mcpp-generated src/generated.cpp\nint main() { return answer() == 42 ? 0 : 1; }\n" + } + ] + }, + { + "project": "c-ares", + "text": "# c-ares test project: consumes compat.c-ares and asserts behavior under\n# `mcpp test`. Part of the mcpp-index self-referential workspace — the\n# workspace-root `[indices]` redirect points at this repo, so the dependency\n# resolves to the checked-in recipe (pkgs/c/compat.c-ares.lua).\n#\n# The test is entirely OFFLINE: it never sends a DNS query, so it does not\n# depend on the CI runner having (or not having) working name resolution.\n[package]\nname = \"c-ares-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nc-ares = \"1.34.5\"\n", + "files": [ + { + "path": "tests/examples/c-ares/tests/resolver.cpp", + "text": "// Behavioral test for compat.c-ares.\n//\n// Entirely OFFLINE — no DNS query is ever sent, so the result does not depend\n// on the runner's network. What it does exercise is the machinery gRPC's\n// resolver actually leans on, and each piece lives in a different part of the\n// library:\n//\n// ares_library_init / _cleanup -> src/lib/ares_library_init.c\n// ares_init_options / ares_destroy -> ares_init.c, ares_options.c, util/*\n// set/get servers as CSV -> ares_options.c, str/*, record/* (parsing\n// an address list is real string+record work)\n// ares_inet_pton / ares_inet_ntop -> inet_net_pton.c, inet_ntop.c\n// ares_strerror -> ares_strerror.c\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n\n#include \"ares.h\"\n\nnamespace {\n\nbool version_ok() {\n int num = 0;\n const char* s = ares_version(&num);\n // The descriptor pins 1.34.5; assert the header/library agree with it, so a\n // silently different vendored version cannot pass.\n return s != nullptr && std::string(s).find(\"1.34.5\") != std::string::npos &&\n ((num >> 16) & 0xff) == 1 && ((num >> 8) & 0xff) == 34 && (num & 0xff) == 5;\n}\n\nbool channel_and_servers_ok() {\n struct ares_options opts;\n std::memset(&opts, 0, sizeof(opts));\n opts.flags = ARES_FLAG_NOSEARCH | ARES_FLAG_NOALIASES;\n\n ares_channel_t* ch = nullptr;\n if (ares_init_options(&ch, &opts, ARES_OPT_FLAGS) != ARES_SUCCESS) return false;\n\n bool ok = false;\n // Setting servers parses the CSV into c-ares' internal server list; reading\n // it back re-serializes it. A stub would not survive the round trip.\n if (ares_set_servers_csv(ch, \"127.0.0.1:5353,[::1]:5354\") == ARES_SUCCESS) {\n char* csv = ares_get_servers_csv(ch);\n if (csv != nullptr) {\n const std::string got(csv);\n ok = got.find(\"127.0.0.1\") != std::string::npos &&\n got.find(\"5353\") != std::string::npos &&\n got.find(\"::1\") != std::string::npos;\n ares_free_string(csv);\n }\n }\n\n // Garbage must be REJECTED — otherwise the \"parse\" above proves nothing.\n if (ok && ares_set_servers_csv(ch, \"not-an-address:::\") == ARES_SUCCESS) ok = false;\n\n ares_destroy(ch);\n return ok;\n}\n\nbool inet_conversions_ok() {\n unsigned char buf[16];\n char out[64];\n\n if (ares_inet_pton(AF_INET, \"192.0.2.7\", buf) != 1) return false;\n if (ares_inet_ntop(AF_INET, buf, out, sizeof(out)) == nullptr) return false;\n if (std::string(out) != \"192.0.2.7\") return false;\n\n if (ares_inet_pton(AF_INET6, \"2001:db8::1\", buf) != 1) return false;\n if (ares_inet_ntop(AF_INET6, buf, out, sizeof(out)) == nullptr) return false;\n if (std::string(out) != \"2001:db8::1\") return false;\n\n // Malformed input must fail.\n return ares_inet_pton(AF_INET, \"999.1.1.1\", buf) != 1;\n}\n\nbool strerror_ok() {\n const char* ok = ares_strerror(ARES_SUCCESS);\n const char* nf = ares_strerror(ARES_ENOTFOUND);\n return ok != nullptr && nf != nullptr && std::strlen(nf) > 0 &&\n std::string(ok) != std::string(nf);\n}\n\n} // namespace\n\nint main() {\n if (ares_library_init(ARES_LIB_INIT_ALL) != ARES_SUCCESS) return 1;\n\n const bool ok = version_ok() && channel_and_servers_ok() &&\n inet_conversions_ok() && strerror_ok();\n\n ares_library_cleanup();\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "cairo", + "text": "# cairo test member — draws, reads the pixels back, and checks the feature split.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"cairo-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# No `features` named, so the package's own default set applies — which is the\n# thing under test: ft/fc/png on, xlib/xcb off.\n[target.'cfg(linux)'.dependencies.freedesktop]\ncairo = \"1.18.2\"\n", + "files": [ + { + "path": "tests/examples/cairo/tests/cairo.cpp", + "text": "// cairo, via its HEADERS — draw something, read the pixels back, and check that the\n// feature split actually took effect.\n//\n// Cairo needs no display and no fonts on disk to draw: an image surface is\n// memory, and that is what a compositor's software fallback and every\n// screenshot path use. So this test draws for real rather than only checking\n// that symbols resolve.\n//\n// THE FEATURE ASSERTIONS ARE THE POINT OF THE PACKAGE. `xlib` and `xcb` are off\n// by default, and \"off\" has to mean the macro is undefined — otherwise a\n// consumer could `#include ` and fail at link instead of at\n// compile, which is the wrong place to find out.\n\n#ifdef __linux__\n\n#include \n// ⚠️ cairo.h does NOT reach the feature headers. This name used to arrive from\n// the module — which is precisely the confusion this split removes: on the\n// header route you include what you use.\n#include \n\n#include \n#include \n\n// ⚠️ THIS FILE IS THE HEADER ROUTE, AND IT NO LONGER IMPORTS.\n//\n// It used to do BOTH — `#include ` and `import freedesktop.cairo;` —\n// and that is why nobody noticed the module was incomplete: the header\n// supplied every name the module was missing, including `cairo_t` and all 192\n// enumerators. An import that is never asked to stand on its own is not\n// tested, it is decorated.\n//\n// `tests/module.cpp` next door takes the module route alone. Two files,\n// because on GCC the two routes cannot be combined in one TU anyway: reaching\n// a system header both textually and through a module's global fragment makes\n// the same type two entities.\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\" cairo %s\\n\", cairo_version_string());\n check(cairo_version() >= 11802, \"cairo_version reports 1.18.2 or newer\");\n\n // ── 1. Draw, then read the pixel back ────────────────────────────────\n // A 64x64 ARGB32 surface, filled with an exact colour, then one pixel\n // sampled. Exact because cairo_set_source_rgb takes doubles and\n // 0.25/0.5/0.75 land on 64/128/191 without rounding ambiguity — the same\n // values the GBM/EGL closed-loop test uses, for the same reason.\n cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 64, 64);\n check(cairo_surface_status(s) == CAIRO_STATUS_SUCCESS,\n \"cairo_image_surface_create(ARGB32, 64, 64)\");\n\n cairo_t *cr = cairo_create(s);\n check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, \"cairo_create on it\");\n\n // ONE drawing pass, THEN one flush, THEN read. The first version of this\n // test flushed and read between the paint and the stroke, and the stroke\n // then changed zero pixels with `cairo_status` still SUCCESS — cairo had\n // handed the buffer out and did not take it back. `cairo_surface_flush` is\n // the end of a drawing sequence, not a checkpoint inside one.\n cairo_set_source_rgb(cr, 0.25, 0.5, 0.75);\n cairo_paint(cr);\n\n cairo_set_source_rgb(cr, 1, 1, 1);\n cairo_set_line_width(cr, 8);\n cairo_move_to(cr, 8, 8);\n cairo_line_to(cr, 56, 56);\n cairo_stroke(cr);\n\n check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, \"…and the context is still clean\");\n cairo_surface_flush(s);\n\n const unsigned char *d = cairo_image_surface_get_data(s);\n const int stride = cairo_image_surface_get_stride(s);\n\n // ARGB32 is little-endian: B G R A.\n // (56,8) is far from the diagonal, so it still carries the paint —\n // 0.25/0.5/0.75 land on 64/128/191 with no rounding ambiguity, the same\n // values the GBM/EGL closed-loop test uses and for the same reason.\n const unsigned char *bg = d + 8 * stride + 56 * 4;\n std::printf(\" (56,8) = %u %u %u %u (B G R A)\\n\", bg[0], bg[1], bg[2], bg[3]);\n check(bg[2] == 64 && bg[1] == 128 && bg[0] == 191 && bg[3] == 255,\n \"the painted background is exactly what was asked for\");\n\n // (32,32) is on the diagonal, under an 8px white stroke.\n const unsigned char *on = d + 32 * stride + 32 * 4;\n std::printf(\" (32,32) = %u %u %u %u\\n\", on[0], on[1], on[2], on[3]);\n // ── 3. The path machinery, checked directly ─────────────────────────\n //\n // These three are here because of what they caught. With\n // `WORDS_BIGENDIAN` defined to 0 — which cairo tests with `#ifdef`, so 0\n // means BIG-ENDIAN — every path got garbage fixed-point coordinates while\n // `cairo_status` reported success and `cairo_paint` kept working:\n //\n // path_extents -8.03e+06 -8.03e+06 4.37e+06 4.37e+06\n // in_fill(40, 40) 1 (the point is outside the rectangle)\n //\n // The pixel check above catches it too, but only as \"the stroke drew\n // nothing\", which sends you looking for a missing source file. These say\n // where the arithmetic went wrong.\n {\n cairo_t *c3 = cairo_create(s);\n cairo_rectangle(c3, 4, 4, 16, 16);\n double x1, y1, x2, y2;\n cairo_path_extents(c3, &x1, &y1, &x2, &y2);\n std::printf(\" path_extents = %g %g %g %g\\n\", x1, y1, x2, y2);\n check(x1 == 4 && y1 == 4 && x2 == 20 && y2 == 20,\n \"a rectangle path has the extents it was given\");\n check(cairo_in_fill(c3, 10, 10) == 1, \"…and a point inside it reads as inside\");\n check(cairo_in_fill(c3, 40, 40) == 0, \"…and a point outside reads as outside\");\n cairo_destroy(c3);\n }\n\n check(on[0] == 255 && on[1] == 255 && on[2] == 255,\n \"…and the stroked diagonal covers the centre in white\");\n\n\n cairo_destroy(cr);\n cairo_surface_destroy(s);\n\n // ── 3. The default feature set ───────────────────────────────────────\n#ifdef CAIRO_HAS_FT_FONT\n check(true, \"feature ft is ON by default (CAIRO_HAS_FT_FONT)\");\n#else\n check(false, \"feature ft should be ON by default\");\n#endif\n#ifdef CAIRO_HAS_FC_FONT\n check(true, \"feature fc is ON by default (CAIRO_HAS_FC_FONT)\");\n#else\n check(false, \"feature fc should be ON by default\");\n#endif\n#ifdef CAIRO_HAS_PNG_FUNCTIONS\n check(true, \"feature png is ON by default\");\n#else\n check(false, \"feature png should be ON by default\");\n#endif\n\n // ── 4. X11 is OFF, and that is the whole point ───────────────────────\n // A Wayland program must not acquire libX11 by depending on cairo. If this\n // ever fails, someone made a backend unconditional.\n#ifdef CAIRO_HAS_XLIB_SURFACE\n check(false, \"xlib must be OFF unless the consumer asks for it\");\n#else\n check(true, \"feature xlib is OFF by default — no libX11 pulled in\");\n#endif\n#ifdef CAIRO_HAS_XCB_SURFACE\n check(false, \"xcb must be OFF unless the consumer asks for it\");\n#else\n check(true, \"feature xcb is OFF by default\");\n#endif\n\n // ── 5. The module carries what the features enabled ──────────────────\n // `cairo_image_surface_create_from_png` lives behind CAIRO_HAS_PNG_FUNCTIONS\n // inside cairo.h itself, not in a feature header — the module generator has\n // to notice that or a png-enabled consumer cannot reach it through `import`.\n check(&cairo_surface_write_to_png != nullptr,\n \"module exports a name guarded inside cairo.h (write_to_png)\");\n check(&cairo_ft_font_face_create_for_ft_face != nullptr,\n \"…and one from a feature header (cairo_ft_font_face_create…)\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/cairo/tests/module.cpp", + "text": "// cairo, via its MODULE — and this is the route that had never been checked.\n//\n// ⭐ WHY THIS FILE EXISTS\n//\n// The package exposed `import freedesktop.cairo;` and the only test that named\n// it also wrote `#include `. So the header supplied whatever the\n// module lacked, and what it lacked was substantial:\n//\n// 470 names exported, and among the missing:\n// cairo_t the single most-used type in the library\n// 192 enumerators every CAIRO_FORMAT_*, CAIRO_STATUS_*, CAIRO_OPERATOR_*\n//\n// It was found from OUTSIDE, by gnome.pangocairo — which cannot mix the two\n// routes (pangocairo.h reaches glib and therefore , so a textual\n// include alongside the import makes `struct _IO_FILE` two entities) and\n// therefore had to ask the module for `cairo_t` and got nothing.\n//\n// This file includes NOTHING. That is the whole point: if a name is missing\n// from the module, this fails to compile.\n\n#ifdef __linux__\n\nimport cairo;\n\n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\"import cairo — %s\\n\\n\", cairo_version_string());\n\n // ── the types, including the one that used to be missing ─────────────\n cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 64, 64);\n check(cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS,\n \"cairo_image_surface_create — CAIRO_FORMAT_ARGB32 is an ENUMERATOR\");\n check(cairo_image_surface_get_format(surf) == CAIRO_FORMAT_ARGB32,\n \"…and the surface reports the format that was asked for\");\n\n // ⭐ `cairo_t` — `typedef struct _cairo cairo_t;`. The old regex required at\n // least one character between `cairo_` and `_t`, so this one name, the one\n // every cairo program starts with, was the one it could not match.\n cairo_t *cr = cairo_create(surf);\n check(cairo_status(cr) == CAIRO_STATUS_SUCCESS, \"cairo_create returns a live cairo_t\");\n\n // ── draw, and read the pixels back ───────────────────────────────────\n cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);\n cairo_rectangle(cr, 8, 8, 48, 48);\n cairo_fill(cr);\n cairo_surface_flush(surf);\n\n const unsigned char *d = cairo_image_surface_get_data(surf);\n const int stride = cairo_image_surface_get_stride(surf);\n check(d != nullptr, \"the image surface has readable pixel data\");\n\n long ink = 0;\n for (int y = 0; d && y < 64; ++y) {\n for (int x = 0; x < 64; ++x) {\n if (d[static_cast(y) * stride + 4 * x + 3] != 0) {\n ++ink;\n }\n }\n }\n std::printf(\" filled pixels: %ld (expected 48*48 = 2304)\\n\", ink);\n check(ink == 48 * 48, \"the rectangle is exactly the pixels it asked for\");\n\n // ── more enumerators, from three different enums ─────────────────────\n cairo_set_operator(cr, CAIRO_OPERATOR_OVER);\n check(cairo_get_operator(cr) == CAIRO_OPERATOR_OVER, \"CAIRO_OPERATOR_OVER\");\n cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);\n check(cairo_get_line_cap(cr) == CAIRO_LINE_CAP_ROUND, \"CAIRO_LINE_CAP_ROUND\");\n cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);\n check(cairo_get_fill_rule(cr) == CAIRO_FILL_RULE_EVEN_ODD, \"CAIRO_FILL_RULE_EVEN_ODD\");\n\n // ── the matrix, which is a struct type rather than an opaque handle ──\n cairo_matrix_t m;\n cairo_matrix_init_identity(&m);\n cairo_matrix_translate(&m, 3.0, 4.0);\n check(m.x0 == 3.0 && m.y0 == 4.0, \"cairo_matrix_t is a usable value type\");\n\n // ── the FreeType font backend, which is a default feature ────────────\n // ⚠️ The point is the ENUMERATOR: CAIRO_FONT_TYPE_FT is what a consumer\n // compares against, and it was among the 192 that never made it out.\n cairo_font_face_t *ff = cairo_get_font_face(cr);\n std::printf(\" default font backend: %d (CAIRO_FONT_TYPE_FT=%d)\\n\",\n (int) cairo_font_face_get_type(ff), (int) CAIRO_FONT_TYPE_FT);\n check(cairo_font_face_status(ff) == CAIRO_STATUS_SUCCESS,\n \"the default font face is live\");\n\n cairo_destroy(cr);\n cairo_surface_destroy(surf);\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\n#include \nint main() { std::printf(\"cairo: Linux only here.\\n\"); return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "capi-lua", + "text": "[indices]\n\"mcpplibs.capi\" = { path = \"../../..\" }\n\n[package]\nname = \"capi-lua-tests\"\nversion = \"0.1.0\"\n\n[dependencies.mcpplibs]\ncapi.lua = \"0.0.3\"\n", + "files": [ + { + "path": "tests/examples/capi-lua/tests/main.cpp", + "text": "import mcpplibs.capi.lua;\n\nnamespace lua = mcpplibs::capi::lua;\n\nint main() {\n auto* state = lua::L_newstate();\n if (state == nullptr) return 1;\n\n const int status = lua::L_dostring(state, \"return 6 * 7\");\n const auto answer = lua::tointeger(state, -1);\n lua::close(state);\n return status == lua::OK && answer == 42 ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "catch2-main", + "text": "# compat.catch2 at v3 — `main` FEATURE path: the package supplies the entry\n# point and this project deliberately defines no main(). If the feature stops\n# gating the generated TU in, this member fails with\n# `undefined reference to 'main'`.\n#\n# This cannot share a project with tests/examples/catch2: features are resolved\n# per consuming project, so one project cannot hold catch2 both with and\n# without `main` (same constraint as asio-ssl vs asio-module).\n#\n# The `compat` index redirect is inherited from the workspace root.\n[package]\nname = \"catch2-main-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ncatch2 = { version = \"3.15.2\", features = [\"main\"] }\n", + "files": [ + { + "path": "tests/examples/catch2-main/tests/main.cpp", + "text": "// compat.catch2 v3, `main` FEATURE: no main() here on purpose — the entry\n// point comes from the package's generated TU. Catch2 prints the assertion\n// count on exit, so an empty binary cannot pass quietly.\n#include \n\nstatic unsigned int factorial(unsigned int n) {\n return n <= 1 ? 1 : n * factorial(n - 1);\n}\n\nTEST_CASE(\"factorial of 5 is 120\", \"[math]\") {\n REQUIRE(factorial(5) == 120);\n}\n\nTEST_CASE(\"factorial of 0 is 1\", \"[math]\") {\n REQUIRE(factorial(0) == 1);\n}\n" + } + ] + }, + { + "project": "catch2-v2-main", + "text": "# compat.catch2 at v2 — `main` FEATURE path: no main() and no\n# CATCH_CONFIG_MAIN here; both come from the package's generated TU. This is\n# also what exercises the ELSE branch of that TU's\n# __has_include() discriminator — the v3 members only\n# ever take the THEN branch.\n#\n# The `compat` index redirect is inherited from the workspace root.\n[package]\nname = \"catch2-v2-main-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ncatch2 = { version = \"2.13.10\", features = [\"main\"] }\n", + "files": [ + { + "path": "tests/examples/catch2-v2-main/tests/main.cpp", + "text": "// compat.catch2 v2, `main` FEATURE: neither main() nor CATCH_CONFIG_MAIN here\n// — both come from the package's generated TU, which reaches this version\n// through the ELSE branch of its __has_include() test.\n#include \n\nstatic unsigned int factorial(unsigned int n) {\n return n <= 1 ? 1 : n * factorial(n - 1);\n}\n\nTEST_CASE(\"factorial of 5 is 120\", \"[math]\") {\n REQUIRE(factorial(5) == 120);\n}\n\nTEST_CASE(\"factorial of 0 is 1\", \"[math]\") {\n REQUIRE(factorial(0) == 1);\n}\n" + } + ] + }, + { + "project": "catch2-v2", + "text": "# compat.catch2 at v2 — DEFAULT path: header-only, this project defines\n# CATCH_CONFIG_MAIN itself. This is the member that proves the single-package\n# union holds: at 2.13.10 the `*/src/catch2/**/*.cpp` glob must match nothing\n# and `*/single_include` must resolve, or the build picks up the wrong half.\n#\n# The `compat` index redirect is inherited from the workspace root.\n# Part of the mcpp-index self-referential workspace.\n[package]\nname = \"catch2-v2-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ncatch2 = \"2.13.10\"\n", + "files": [ + { + "path": "tests/examples/catch2-v2/tests/main.cpp", + "text": "#define CATCH_CONFIG_MAIN\n#include \n\nstatic unsigned int factorial(unsigned int n) {\n return n <= 1 ? 1 : n * factorial(n - 1);\n}\n\nTEST_CASE(\"factorial of 5 is 120\", \"[math]\") {\n REQUIRE(factorial(5) == 120);\n}\n\nTEST_CASE(\"factorial of 0 is 1\", \"[math]\") {\n REQUIRE(factorial(0) == 1);\n}\n" + } + ] + }, + { + "project": "catch2", + "text": "# compat.catch2 at v3 — DEFAULT path: the package is a static library and this\n# project supplies its own main() via Catch::Session. Also pins down that\n# upstream's catch_main.cpp really is excluded — if the `!` negation stopped\n# working, this member would fail with `multiple definition of 'main'`.\n#\n# The `compat` index redirect is inherited from the workspace root.\n# Part of the mcpp-index self-referential workspace.\n[package]\nname = \"catch2-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ncatch2 = \"3.15.2\"\n", + "files": [ + { + "path": "tests/examples/catch2/tests/main.cpp", + "text": "#include \n\nstatic unsigned int factorial(unsigned int n) {\n return n <= 1 ? 1 : n * factorial(n - 1);\n}\n\nTEST_CASE(\"factorial of 5 is 120\", \"[math]\") {\n REQUIRE(factorial(5) == 120);\n}\n\nTEST_CASE(\"factorial of 0 is 1\", \"[math]\") {\n REQUIRE(factorial(0) == 1);\n}\n\nint main(int argc, char* argv[]) {\n return Catch::Session().run(argc, argv);\n}\n" + } + ] + }, + { + "project": "cjson", + "text": "# cJSON test project: depends on compat.cjson (built from source via this repo's\n# own index) and asserts behavior under `mcpp test`. Part of the mcpp-index\n# self-referential workspace.\n[package]\nname = \"cjson-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ncjson = \"1.7.19\"\n", + "files": [ + { + "path": "tests/examples/cjson/tests/parse.cpp", + "text": "// Behavioral test: build a cJSON document, serialize, re-parse, assert fields.\n#include \n#include \n#include \n\nint main() {\n cJSON* root = cJSON_CreateObject();\n cJSON_AddNumberToObject(root, \"answer\", 42);\n cJSON_AddStringToObject(root, \"lib\", \"cJSON\");\n char* text = cJSON_PrintUnformatted(root);\n\n cJSON* parsed = cJSON_Parse(text);\n assert(parsed != nullptr);\n assert(cJSON_GetObjectItem(parsed, \"answer\")->valueint == 42);\n assert(std::strcmp(cJSON_GetObjectItem(parsed, \"lib\")->valuestring, \"cJSON\") == 0);\n\n cJSON_free(text);\n cJSON_Delete(root);\n cJSON_Delete(parsed);\n return 0;\n}\n" + } + ] + }, + { + "project": "cli11", + "text": "# CLI11 test project: consumes compat.CLI11 (header-only command line parser)\n# from this repo's own index and asserts real argv parsing under `mcpp test`.\n# Part of the mcpp-index self-referential workspace: the `compat` redirect is\n# declared at the workspace root and inherited here, so the dependency\n# resolves to the checked-in recipe (pkgs/c/compat.CLI11.lua) rather than to\n# the published remote index.\n#\n# The dependency is spelled QUALIFIED (`[dependencies.compat]`) on purpose:\n# the index redirect is keyed by the REQUEST's namespace, so a bare\n# `CLI11 = \"2.7.2\"` would ask under the default namespace, resolve from the\n# published index, and quietly stop testing this checkout.\n[package]\nname = \"cli11-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nCLI11 = \"2.7.2\"\n", + "files": [ + { + "path": "tests/examples/cli11/tests/parse.cpp", + "text": "// Behavioral test: parse synthetic argv with CLI11 and assert what came out.\n// Covers the four things a parser has to get right — values and defaults,\n// flags, a multi-value option, a subcommand — plus the two failure paths\n// (unknown option, failed validator), which is what proves the parse actually\n// ran rather than silently doing nothing. Returns non-zero on any mismatch.\n//\n// It also calls the package's anchor symbol. compat.CLI11 is header-only, so\n// its lib target holds exactly that one translation unit: if the reference\n// resolves, the package really was built and linked, not just included.\n#include \n\n#include \n#include \n\nint mcpp_compat_cli11_headers_anchor(void);\n\nnamespace {\n\n// Values, default, flag, multi-value option and a subcommand, in one parse.\nbool parses_a_full_command_line() {\n CLI::App app{\"mcpp-index CLI11 member\"};\n\n std::string name;\n app.add_option(\"-n,--name\", name, \"a name\")->required();\n\n int count = 1; // left alone by the argv below: asserts the default holds\n app.add_option(\"-c,--count\", count, \"how many\");\n\n bool verbose = false;\n app.add_flag(\"-v,--verbose\", verbose, \"chatty\");\n\n std::vector nums;\n app.add_option(\"--nums\", nums, \"numbers\")->expected(3);\n\n auto *sub = app.add_subcommand(\"run\", \"do the thing\");\n std::string target;\n sub->add_option(\"target\", target, \"what to run\")->required();\n\n const char *argv[] = {\"prog\", \"--name\", \"mcpp\", \"-v\",\n \"--nums\", \"1\", \"2\", \"3\", \"run\", \"index\"};\n try {\n app.parse(static_cast(sizeof(argv) / sizeof(argv[0])), argv);\n } catch (const CLI::ParseError &) {\n return false;\n }\n\n return name == \"mcpp\" && count == 1 && verbose\n && nums == std::vector{1, 2, 3}\n && app.got_subcommand(sub) && target == \"index\";\n}\n\n// An option nobody declared must be rejected, not ignored.\nbool rejects_an_unknown_option() {\n CLI::App app;\n int count = 0;\n app.add_option(\"-c,--count\", count);\n\n const char *argv[] = {\"prog\", \"--nope\"};\n try {\n app.parse(2, argv);\n } catch (const CLI::ParseError &) {\n return true;\n }\n return false;\n}\n\n// Validators live in their own header; a value outside the range must throw.\nbool enforces_a_validator() {\n CLI::App app;\n int count = 0;\n app.add_option(\"-c,--count\", count)->check(CLI::Range(1, 10));\n\n const char *argv[] = {\"prog\", \"-c\", \"99\"};\n try {\n app.parse(3, argv);\n } catch (const CLI::ValidationError &) {\n return true;\n } catch (const CLI::ParseError &) {\n return false;\n }\n return false;\n}\n\n} // namespace\n\nint main() {\n const bool ok = parses_a_full_command_line()\n && rejects_an_unknown_option()\n && enforces_a_validator()\n && std::string(CLI11_VERSION) == \"2.7.2\"\n && mcpp_compat_cli11_headers_anchor() == 0;\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "cmdline", + "text": "# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `default`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\ndefault = { path = \"../../..\" }\n\n[package]\nname = \"cmdline-tests\"\nversion = \"0.1.0\"\n\n[dependencies]\ncmdline = \"0.0.2\"\n", + "files": [ + { + "path": "tests/examples/cmdline/tests/consume.cpp", + "text": "// Consuming `mcpplibs.cmdline` through the published index.\n//\n// `parse_from` rather than `run(argc, argv)`: a test whose only assertion is\n// \"it linked\" passes for a package that parses nothing. Parsing a string and\n// checking what came out is the difference between testing the dependency\n// edge and testing the dependency.\n//\n// The chains end in `.end()`, which is what commits the pending item and\n// hands back the App — a builder is not an App, and the compiler says so.\nimport std;\nimport mcpplibs.cmdline;\n\nusing namespace mcpplibs;\n\nint main() {\n int failures = 0;\n auto check = [&](bool ok, std::string_view what) {\n if (!ok) { std::println(\"FAIL: {}\", what); ++failures; }\n };\n\n auto app = cmdline::App(\"tool\")\n .version(\"1.0\")\n .arg(\"cmd\").required()\n .option(\"verbose\").short_name('v')\n .option(\"config\").short_name('c').takes_value().value_name(\"FILE\")\n .end();\n\n auto parsed = app.parse_from(\"tool add --verbose --config=out.toml\");\n check(parsed.has_value(), \"a well-formed command line parses\");\n if (parsed) {\n check(parsed->positional(0) == \"add\", \"positional survives parsing\");\n check(parsed->is_flag_set(\"verbose\"), \"long flag is seen\");\n auto cfg = parsed->value(\"config\");\n check(cfg.has_value() && *cfg == \"out.toml\", \"--opt=value is captured\");\n }\n\n // A required positional that is absent must be refused. Without this, the\n // assertions above are all satisfiable by a parser that accepts anything.\n auto strict = cmdline::App(\"tool\").arg(\"cmd\").required().end();\n auto missing = strict.parse_from(\"tool\");\n check(!missing.has_value() || missing.error().is_error(),\n \"a missing required argument is refused\");\n\n if (failures == 0) std::println(\"cmdline: ok\");\n return failures == 0 ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "cmp-module", + "text": "# A member-level redirect replaces the workspace root's compat redirect, so\n# this test resolves mcpplibs.cmp from the current checkout.\n[indices]\ndefault = { path = \"../../..\" }\n\n[package]\nname = \"cmp-module-tests\"\nversion = \"0.1.0\"\n\n[dependencies]\ncmp = \"0.1.0\"\n", + "files": [ + { + "path": "tests/examples/cmp-module/tests/runtime.cpp", + "text": "import std;\nimport mcpplibs.cmp;\n\nusing mcpplibs::cmp::RunLoop;\nusing mcpplibs::cmp::Task;\n\nTask scheduled_answer(RunLoop::Scheduler scheduler) {\n co_await scheduler.schedule();\n co_return 42;\n}\n\nint main() {\n RunLoop loop {};\n return loop.run(scheduled_answer(loop.get_scheduler())) == 42 ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "concurrentqueue-c-api", + "text": "# concurrentqueue C-API test project: the OTHER half of the package — the\n# `c-api` feature. The base member (tests/examples/concurrentqueue) consumes\n# the default build, which does NOT compile c_api/*.cpp; this one asks for the\n# feature in the long form and asserts the moodycamel_cq_* / moodycamel_bcq_*\n# surface actually resolves and behaves, which is what proves the gate both\n# compiles and links what it names.\n#\n# Same QUALIFIED-dependency reasoning as tests/examples/concurrentqueue: the\n# request must resolve from this checkout's index.\n[package]\nname = \"concurrentqueue-c-api-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nconcurrentqueue = { version = \"1.0.5\", features = [\"c-api\"] }\n", + "files": [ + { + "path": "tests/examples/concurrentqueue-c-api/tests/c_api.cpp", + "text": "// compat.concurrentqueue `c-api` feature — drive the flat extern \"C\" surface\n// over both queues. Values cross the boundary as void*, so the test round-trips\n// integers through MoodycamelValue and checks them on the way out. A package\n// that compiled the wrappers against the wrong queue type (cq vs bcq handles\n// are both void*) would still link and swap semantics — which is exactly the\n// failure these assertions catch.\n#include \n\n#include \n#include \n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, const char* what) {\n if (!cond) {\n std::printf(\"FAIL: %s\\n\", what);\n ok = false;\n }\n };\n\n // ---- plain queue -------------------------------------------------------\n {\n MoodycamelCQHandle q = nullptr;\n check(moodycamel_cq_create(&q) == 1, \"cq_create succeeds\");\n check(q != nullptr, \"cq_create yields a handle\");\n check(moodycamel_cq_size_approx(q) == 0, \"fresh cq reports size 0\");\n\n uint64_t sent = 0;\n for (int i = 0; i < 100; ++i) {\n uintptr_t v = 1000 + i;\n sent += v;\n if (moodycamel_cq_enqueue(q, (MoodycamelValue)v) != 1) {\n check(false, \"cq_enqueue succeeds\");\n break;\n }\n }\n check(moodycamel_cq_size_approx(q) == 100, \"cq_size_approx after 100 enqueues\");\n\n uint64_t got = 0;\n int count = 0;\n MoodycamelValue v = nullptr;\n while (moodycamel_cq_try_dequeue(q, &v) == 1) {\n got += (uintptr_t)v;\n ++count;\n }\n check(count == 100, \"all 100 values come back out\");\n check(got == sent, \"values survive the void* round-trip intact\");\n check(moodycamel_cq_try_dequeue(q, &v) == 0, \"drained cq reports try_dequeue == 0\");\n\n check(moodycamel_cq_destroy(q) == 1, \"cq_destroy succeeds\");\n }\n\n // ---- blocking queue ------------------------------------------------------\n {\n MoodycamelBCQHandle q = nullptr;\n check(moodycamel_bcq_create(&q) == 1, \"bcq_create succeeds\");\n\n MoodycamelValue v = nullptr;\n check(moodycamel_bcq_try_dequeue(q, &v) == 0, \"empty bcq reports try_dequeue == 0\");\n\n for (uintptr_t i = 0; i < 50; ++i) {\n if (moodycamel_bcq_enqueue(q, (MoodycamelValue)i) != 1) {\n check(false, \"bcq_enqueue succeeds\");\n break;\n }\n }\n // wait_dequeue BLOCKS until an item is available; here the items are\n // already queued, so each call returns without hanging — and in order.\n uint64_t got = 0;\n for (int i = 0; i < 50; ++i) {\n if (moodycamel_bcq_wait_dequeue(q, &v) != 1 || (uintptr_t)v != i) {\n check(false, \"bcq_wait_dequeue hands back every value in order\");\n break;\n }\n got += (uintptr_t)v;\n }\n check(got == 50L * 49 / 2, \"bcq values sum to what went in\");\n check(moodycamel_bcq_try_dequeue(q, &v) == 0, \"drained bcq reports try_dequeue == 0\");\n\n check(moodycamel_bcq_destroy(q) == 1, \"bcq_destroy succeeds\");\n }\n\n std::printf(\"%s\\n\", ok ? \"concurrentqueue c-api: all checks passed\"\n : \"concurrentqueue c-api: FAILED\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "concurrentqueue", + "text": "# concurrentqueue test project: drive the C++ API of compat.concurrentqueue\n# (built from source via this repo's own index) under `mcpp test`. Part of the\n# mcpp-index self-referential workspace.\n#\n# The dependency is spelled QUALIFIED (`[dependencies.compat]`) so the request\n# resolves from THIS checkout's index rather than the published remote — the\n# one thing this member exists to test (same reasoning as tests/examples/eigen).\n#\n# What the test asserts, in descending order of \"a broken package would pass\n# this anyway\": real concurrent delivery (4 producers / 4 consumers, every\n# value received EXACTLY once), blocking semantics (a consumer actually blocks\n# and times out), FIFO order, bulk ops, and the move-only element path.\n[package]\nname = \"concurrentqueue-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nconcurrentqueue = \"1.0.5\"\n", + "files": [ + { + "path": "tests/examples/concurrentqueue/tests/queue.cpp", + "text": "// compat.concurrentqueue — exercise the C++ API the way the library's README\n// shows it: enqueue/try_dequeue, bulk ops, a blocking consumer, and a real\n// multi-producer/multi-consumer race. Every check asserts observable\n// behaviour (order, exact-once delivery, timeout), so a package that links\n// but miscompiles the lock-free paths still fails here.\n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, const char* what) {\n if (!cond) {\n std::printf(\"FAIL: %s\\n\", what);\n ok = false;\n }\n };\n\n // ---- single-threaded FIFO --------------------------------------------\n // The queue is MPMC, not ordered ACROSS producers — but a single producer\n // hands its own items to the consumer in order, and that is what we hold\n // it to here.\n {\n moodycamel::ConcurrentQueue q;\n check(q.size_approx() == 0, \"fresh queue reports size 0\");\n for (int i = 0; i < 1000; ++i) q.enqueue(i);\n check(q.size_approx() == 1000, \"size_approx after 1000 enqueues\");\n\n int v = -1;\n for (int i = 0; i < 1000; ++i) {\n if (!q.try_dequeue(v) || v != i) {\n check(false, \"single-producer FIFO order preserved\");\n break;\n }\n }\n check(!q.try_dequeue(v), \"drained queue reports try_dequeue == false\");\n check(q.size_approx() == 0, \"size_approx back to 0 after draining\");\n }\n\n // ---- bulk ops ---------------------------------------------------------\n {\n moodycamel::ConcurrentQueue q;\n std::vector in(5000);\n for (int i = 0; i < 5000; ++i) in[i] = i;\n // enqueue_bulk answers YES/NO (bool); only try_dequeue_bulk answers\n // HOW MANY (size_t). Mixing the two up is exactly the kind of mistake\n // this test exists to catch — starting with its own author's.\n check(q.enqueue_bulk(in.data(), in.size()),\n \"enqueue_bulk reports everything enqueued\");\n\n std::vector out(5000, -1);\n check(q.try_dequeue_bulk(out.data(), out.size()) == out.size(),\n \"try_dequeue_bulk reports everything dequeued\");\n check(in == out, \"bulk round-trip preserves contents and order\");\n }\n\n // ---- producer token ----------------------------------------------------\n // Tokens are the documented fast path for a dedicated producer; exercise\n // at least the enqueue side of them.\n {\n moodycamel::ConcurrentQueue q;\n moodycamel::ProducerToken ptok(q);\n q.enqueue(ptok, 7);\n int v = 0;\n check(q.try_dequeue(v) && v == 7, \"token enqueue round-trips\");\n }\n\n // ---- move-only elements ------------------------------------------------\n {\n moodycamel::ConcurrentQueue> q;\n q.enqueue(std::make_unique(41));\n q.enqueue(std::make_unique(42));\n std::unique_ptr p;\n check(q.try_dequeue(p) && p && *p == 41, \"move-only element survives the queue\");\n check(q.try_dequeue(p) && p && *p == 42, \"second move-only element comes out in order\");\n check(!q.try_dequeue(p), \"queue drains after move-only elements\");\n }\n\n // ---- blocking consumer -------------------------------------------------\n // wait_dequeue must actually block until the producer lands an item, and\n // wait_dequeue_timed on an EMPTY queue must come back false after the\n // timeout — the second half is what separates \"semaphore posted eagerly\"\n // from a real wait.\n {\n moodycamel::BlockingConcurrentQueue q;\n\n int missed = -1;\n auto t0 = std::chrono::steady_clock::now();\n bool got = q.wait_dequeue_timed(missed, std::chrono::milliseconds(50));\n auto elapsed = std::chrono::steady_clock::now() - t0;\n check(!got, \"wait_dequeue_timed on an empty queue times out\");\n check(elapsed >= std::chrono::milliseconds(45),\n \"the timeout was actually waited, not spun through\");\n\n std::thread producer([&q] {\n for (int i = 0; i < 2000; ++i) q.enqueue(i);\n });\n int prev = -1;\n for (int i = 0; i < 2000; ++i) {\n int v = -1;\n // wait_dequeue returns void in 1.0.5 (only the _timed spelling\n // reports success), so the assertion is on the VALUE alone: if\n // the semaphore path were broken this call would either hang\n // (test timeout) or hand back a value out of sequence.\n q.wait_dequeue(v);\n if (v != prev + 1) {\n check(false, \"blocking consumer receives every item in order\");\n break;\n }\n prev = v;\n }\n producer.join();\n int drained = -1;\n check(!q.wait_dequeue_timed(drained, std::chrono::milliseconds(50)),\n \"nothing left after the consumer caught up\");\n }\n\n // ---- MPMC: exact-once delivery under contention -------------------------\n // 4 producers x 10000 unique values, 4 consumers racing them out. Every\n // value must arrive EXACTLY once: a lost slot, a duplicated slot, or an\n // ABA-shaped torn read all show up as a counter != 1. This is the check\n // the single-threaded assertions cannot make.\n {\n constexpr int kProducers = 4, kConsumers = 4, kPerProducer = 10000;\n constexpr int kTotal = kProducers * kPerProducer;\n\n moodycamel::ConcurrentQueue q;\n std::vector> seen(kTotal);\n for (auto& s : seen) s.store(0, std::memory_order_relaxed);\n std::atomic dequeued{0};\n\n std::vector producers, consumers;\n for (int p = 0; p < kProducers; ++p) {\n producers.emplace_back([&q, p] {\n for (int i = 0; i < kPerProducer; ++i) q.enqueue(p * kPerProducer + i);\n });\n }\n for (int c = 0; c < kConsumers; ++c) {\n consumers.emplace_back([&] {\n int v;\n // try_dequeue until the producers are done AND the queue is\n // empty — the consumer that would otherwise spin on an empty\n // queue while producers are still working is the whole point\n // of a concurrent queue.\n for (;;) {\n while (q.try_dequeue(v)) {\n seen[v].store(1, std::memory_order_relaxed);\n dequeued.fetch_add(1, std::memory_order_relaxed);\n }\n if (dequeued.load(std::memory_order_relaxed) >= kTotal) return;\n std::this_thread::yield();\n }\n });\n }\n for (auto& t : producers) t.join();\n for (auto& t : consumers) t.join();\n\n check(dequeued.load() == kTotal, \"every enqueued value was dequeued\");\n int exactly_once = 0;\n for (const auto& s : seen) exactly_once += (s.load() == 1) ? 1 : 0;\n check(exactly_once == kTotal, \"every value delivered exactly once (no loss, no duplicate)\");\n }\n\n std::printf(\"%s\\n\", ok ? \"concurrentqueue: all checks passed\" : \"concurrentqueue: FAILED\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "core", + "text": "# Core compat libraries: gtest (test framework), ftxui (TUI), lua, mbedtls\n# (crypto), opengl + khrplatform (headers). Headless behavioral assertions via\n# `mcpp test`. Cross-platform: toolchain auto-selects per OS; link flags per OS.\n[package]\nname = \"core-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngtest = { version = \"1.15.2\", features = [\"main\"] }\nftxui = \"7.0.3\"\nlua = \"5.4.7\"\nmbedtls = \"3.6.1\"\nopengl = \"2026.05.31\"\nkhrplatform = \"2026.05.31\"\n\n# Per-OS link flags (was the portable smoke's write_build_ldflags()).\n[target.'cfg(linux)'.build]\nldflags = [\"-ldl\", \"-lm\"]\n[target.'cfg(macos)'.build]\nldflags = [\"-lm\"]\n", + "files": [ + { + "path": "tests/examples/core/tests/runtime.cpp", + "text": "// Faithful migration of smoke_compat_core.sh: gtest + ftxui + lua + mbedtls +\n// OpenGL/KHR headers. main() comes from the gtest \"main\" feature.\n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n\nextern \"C\" {\n#include \n#include \n#include \n}\n\nstatic bool check_ftxui() {\n using namespace ftxui;\n Element document = hbox({text(\"compat\"), separator(), text(\"ftxui\")});\n Screen screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));\n Render(screen, document);\n const std::string rendered = screen.ToString();\n return rendered.find(\"compat\") != std::string::npos &&\n rendered.find(\"ftxui\") != std::string::npos;\n}\nstatic bool check_lua() {\n lua_State* state = luaL_newstate();\n if (!state) return false;\n luaL_openlibs(state);\n const int rc = luaL_dostring(state, \"return 20 + 22\");\n const bool ok = rc == LUA_OK && lua_isinteger(state, -1) && lua_tointeger(state, -1) == 42;\n lua_close(state);\n return ok;\n}\nstatic bool check_mbedtls() {\n const unsigned char input[] = \"abc\";\n std::array out{};\n mbedtls_sha256(input, 3, out.data(), 0);\n return out[0] == 0xba && out[1] == 0x78 && out[30] == 0x15 && out[31] == 0xad;\n}\nstatic bool check_opengl_headers() {\n return GL_TEXTURE_2D == 0x0DE1 && static_cast(1) == 1;\n}\n\nTEST(CompatGTest, BasicAssertion) { EXPECT_EQ(2 + 2, 4); }\nTEST(CompatCore, UpstreamHeadersAndMinimalRuntime) {\n EXPECT_TRUE(check_ftxui());\n EXPECT_TRUE(check_lua());\n EXPECT_TRUE(check_mbedtls());\n EXPECT_TRUE(check_opengl_headers());\n}\n" + } + ] + }, + { + "project": "cpptrace", + "text": "# compat.cpptrace test project. `compat` redirect inherited from the workspace\n# root.\n[package]\nname = \"cpptrace-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ncpptrace = \"1.0.4\"\n", + "files": [ + { + "path": "tests/examples/cpptrace/tests/trace.cpp", + "text": "// Behavioral test — compat.cpptrace unwinds a real call stack.\n//\n// What is asserted is deliberately what the CHOSEN BACKENDS can answer on a\n// bare CI runner. The package selects `_Unwind_Backtrace` + `dladdr` on unix\n// and DbgHelp on windows, all dependency-free, and the documented cost of that\n// choice is that frames carry no file:line (dladdr does not read DWARF). So\n// this test asserts the unwinder — frame count and distinct return addresses —\n// and never asserts on line numbers, which would be testing libdwarf's absence\n// rather than this package.\n#include \n#include \n\n#include \n#include \n\n#if CPPTRACE_VERSION != CPPTRACE_TO_VERSION(1, 0, 4)\n# error \"cpptrace/version.hpp does not describe 1.0.4 — the generated header drifted from the tarball\"\n#endif\n\n// The package builds objects, not a shared library, so every cpptrace\n// declaration this TU sees must be plain — not dllimport, not\n// visibility(\"default\"). The package delivers that with a shim in front of\n// cpptrace/basic.hpp, because no descriptor key carries a define to a\n// consumer's TUs.\n//\n// This check exists because the failure it guards is invisible on Linux: there\n// the difference is only a visibility attribute and everything still links. On\n// the MSVC ABI it is `dllimport` versus nothing, and the link fails with\n// \"undefined symbol: __declspec(dllimport) ...\". Asserting the macro at COMPILE\n// time fails on every platform the moment the shim stops being reached.\n#if !defined(CPPTRACE_STATIC_DEFINE)\n# error \"CPPTRACE_STATIC_DEFINE is not set for this consumer TU — the compat.cpptrace basic.hpp shim was not reached (include_dirs order?)\"\n#endif\n\n// noinline so the three frames cannot be collapsed into one by the optimiser;\n// the test is about the unwinder seeing depth.\n#if defined(_MSC_VER)\n# define NOINLINE __declspec(noinline)\n#else\n# define NOINLINE __attribute__((noinline))\n#endif\n\nstatic NOINLINE cpptrace::stacktrace level_three() { return cpptrace::generate_trace(); }\nstatic NOINLINE cpptrace::stacktrace level_two() { return level_three(); }\nstatic NOINLINE cpptrace::stacktrace level_one() { return level_two(); }\n\nint main() {\n const cpptrace::stacktrace trace = level_one();\n\n if (trace.frames.empty()) {\n std::printf(\"generate_trace returned no frames — the unwinder produced nothing\\n\");\n return 1;\n }\n // main + three levels, minus whatever the runtime trims. Four is the\n // number the chain above must at least reach for the unwind to be real\n // rather than a single-frame stub.\n if (trace.frames.size() < 4) {\n std::printf(\"only %zu frames; expected the four-deep chain to survive\\n\", trace.frames.size());\n return 2;\n }\n\n // Distinct return addresses: a stub that filled the vector with one\n // repeated address would pass the count check but not this one.\n std::uintptr_t distinct = 0;\n for (std::size_t i = 1; i < trace.frames.size(); ++i) {\n if (trace.frames[i].raw_address != trace.frames[i - 1].raw_address) ++distinct;\n }\n if (distinct == 0) {\n std::printf(\"every frame has the same address\\n\");\n return 3;\n }\n if (trace.frames[0].raw_address == 0) {\n std::printf(\"top frame has a null address\\n\");\n return 4;\n }\n\n std::printf(\"compat.cpptrace: ok (%zu frames, %ju distinct addresses, version %d)\\n\",\n trace.frames.size(), static_cast(distinct), CPPTRACE_VERSION);\n return 0;\n}\n" + } + ] + }, + { + "project": "cuda-curand", + "text": "# compat.curand — the CUDA operator libraries as ordinary mcpp dependencies.\n#\n# WHAT THIS ASSERTS ON A RUNNER WITH NO DEVICE, WHICH IS EVERY RUNNER HERE.\n# The failure this package was written against happens BEFORE main: NVIDIA's\n# shared objects carry `RUNPATH = $ORIGIN`, and a non-empty RUNPATH on an\n# intermediate library turns off the executable's inherited DT_RPATH for that\n# library's own dependencies, so the program died with\n#\n# error while loading shared libraries: librt.so.1\n#\n# on a machine that had librt.so.1 in the payload the artifact points at. A test\n# binary that starts at all has proved the farm resolves; the device assertions\n# below then run only where a device answers.\n#\n# ⚠️ ONE OF THE FIVE. compat.cublas / cufft / cusolver / cusparse are generated\n# from the same descriptor template and differ only in the payload they name and\n# the `-l` names they pass, which were read from upstream's own archives. cuRAND\n# carries the smallest payload (85 MB against cuBLAS's 933 MB), so it is the one\n# that runs on every pull request.\n#\n# The `compat` index redirect is inherited from the workspace root.\n[package]\nname = \"cuda-curand-tests\"\nversion = \"0.1.0\"\n\n# Linux-only, like the adapters themselves: the payloads declare `linux` and the\n# loader arrangement the packages exist for has no counterpart elsewhere. The\n# member still builds on all three platforms, which is what keeps the test\n# source from rotting silently on the two that do not exercise it.\n[target.'cfg(linux)'.dependencies.compat]\ncurand = \"10.3.10.19\"\ncuda-driver = \"2026.09.05\"\n", + "files": [ + { + "path": "tests/examples/cuda-curand/tests/curand.cpp", + "text": "// The load path first, the device second.\n//\n// Reaching main is itself the assertion this member exists for: it means every\n// DT_NEEDED of libcurand.so resolved through the farm compat.curand builds,\n// including the C library stubs that the component's own `RUNPATH = $ORIGIN`\n// would otherwise hide.\n#include \n\n// The adapters under test exist only on Linux, so the assertions do too. The\n// file still compiles everywhere, which is what keeps it from rotting silently\n// on the two platforms that do not exercise it.\n#ifndef __linux__\nint main() {\n std::printf(\"not applicable on this platform\\n\");\n return 0;\n}\n#else\n#include \n#include \n\n#include \n\nstatic int failures = 0;\n\nstatic void check(bool ok, const char* what) {\n if (!ok) { std::printf(\"FAIL: %s\\n\", what); ++failures; }\n else std::printf(\"ok: %s\\n\", what);\n}\n\nint main() {\n // ⭐ REACHING THIS LINE IS THE ASSERTION THIS MEMBER EXISTS FOR. Every\n // DT_NEEDED of libcurand.so had to resolve through the farm compat.curand\n // builds, including the C library stubs that the component's own\n // `RUNPATH = $ORIGIN` would otherwise hide. That failure happens before\n // main on every machine, with or without a device.\n int devices = 0;\n const cudaError_t rc = cudaGetDeviceCount(&devices);\n if (rc != cudaSuccess || devices == 0) {\n // Not a failure. A machine with no NVIDIA driver is a legitimate\n // configuration and every runner in this repository is one.\n //\n // ⚠️ THE DEVICE CHECK COMES FIRST, and that ordering is a correction.\n // An earlier revision created the generator before asking, on the\n // theory that a generator is a host-side object. Measured on CI:\n // `curandCreateGenerator` fails with `CUDA driver version is\n // insufficient for CUDA runtime version` on a driverless machine, so\n // the member reported a failure for the one configuration it was\n // written to tolerate.\n std::printf(\"ok: the library loaded and answered; no device here (%s)\\n\",\n cudaGetErrorString(rc));\n return 0;\n }\n\n curandGenerator_t gen{};\n check(curandCreateGenerator(&gen, CURAND_RNG_PSEUDO_DEFAULT) == CURAND_STATUS_SUCCESS,\n \"curandCreateGenerator answers\");\n\n float* dev = nullptr;\n check(cudaMalloc(&dev, 4096 * sizeof(float)) == cudaSuccess, \"cudaMalloc\");\n check(curandSetPseudoRandomGeneratorSeed(gen, 1234ULL) == CURAND_STATUS_SUCCESS,\n \"curandSetPseudoRandomGeneratorSeed\");\n check(curandGenerateUniform(gen, dev, 4096) == CURAND_STATUS_SUCCESS,\n \"curandGenerateUniform\");\n\n float host[4096]{};\n check(cudaMemcpy(host, dev, sizeof host, cudaMemcpyDeviceToHost) == cudaSuccess,\n \"cudaMemcpy back\");\n\n double sum = 0.0;\n bool in_range = true;\n for (float v : host) {\n if (v < 0.0f || v > 1.0f) in_range = false;\n sum += v;\n }\n check(in_range, \"every value lies in [0, 1]\");\n check(std::fabs(sum / 4096.0 - 0.5) < 0.05, \"the mean is near 0.5\");\n\n cudaFree(dev);\n curandDestroyGenerator(gen);\n return failures == 0 ? 0 : 1;\n}\n#endif // __linux__\n" + } + ] + }, + { + "project": "cuda-driver", + "text": "# compat.cuda-driver is a Linux-only adapter: the problem it solves is that an\n# mcpp binary on Linux runs under mcpp's own loader and therefore cannot see the\n# host's driver. macOS and Windows have neither that loader arrangement nor an\n# NVIDIA userspace driver in this shape, so the dependency is conditioned rather\n# than the member being excluded — the member still builds on all three, which\n# is what keeps the test source itself from rotting.\n#\n# ⚠️ `cuda-runtime` IS DECLARED ALONGSIDE IT, AND THAT IS THE POINT. The adapter\n# shipped once under that name and the entry is kept so consumers already\n# writing it keep resolving. A promise nothing exercises is a promise that\n# breaks quietly, so this member depends on both and the build proves the old\n# name still reaches a package. Two farms of the same symlink are harmless: the\n# capability is not exclusive and the artifact's search path lists both.\n[package]\nname = \"cuda-driver-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\ncuda-driver = \"2026.09.05\"\ncuda-runtime = \"2026.09.05\"\n", + "files": [ + { + "path": "tests/examples/cuda-driver/tests/farm.cpp", + "text": "// What compat.cuda-driver is asserted to do, and what it is not.\n//\n// A machine with no NVIDIA driver is a legitimate configuration and is what\n// every runner in this repository is, so the test cannot require a device. It\n// asserts the two properties that hold on both kinds of machine:\n//\n// 1. The package resolves, builds and links. That alone covers the failure\n// this package exists to prevent, because the failure is a LINK-time one:\n// an unversioned libcuda.so harvested into a directory mcpp puts on the\n// link line would be picked up by -lcuda and bind the build to one\n// machine's driver. The patterns are versioned precisely so that cannot\n// happen, and a build that links proves it did not.\n//\n// 2. Where a driver is present, the farm reaches it. Guarded on the driver\n// actually being there rather than skipped by a marker, so the assertion\n// is real on a machine with a GPU and vacuous on one without, and neither\n// case is reported as a pass of the other.\n#include \n\n// The adapter under test exists only on Linux, so the assertion does too. The\n// file still compiles everywhere, which is what keeps it from rotting silently\n// on the two platforms that do not exercise it.\n#ifndef __linux__\nint main() {\n std::printf(\"not applicable on this platform\\n\");\n return 0;\n}\n#else\n#include \n\nint main() {\n // The driver's userspace library, by soname. The farm's whole job is to\n // make this resolve from inside mcpp's own loader, which does not search\n // the host's library path.\n void* h = dlopen(\"libcuda.so.1\", RTLD_LAZY);\n if (!h) {\n // No driver on this machine. The farm is empty, which is correct.\n std::printf(\"no host driver: %s\\n\", dlerror());\n return 0;\n }\n // Present: then the symbol every CUDA runtime looks for must be there too.\n // A farm that linked a stale or wrong-class file would resolve the library\n // and fail here, which is the difference between \"found something\" and\n // \"found the driver\".\n void* sym = dlsym(h, \"cuInit\");\n std::printf(\"host driver present, cuInit=%p\\n\", sym);\n dlclose(h);\n return sym ? 0 : 1;\n}\n#endif\n" + } + ] + }, + { + "project": "curl", + "text": "[package]\nname = \"curl-tests\"\nversion = \"0.1.0\"\n\n# Deliberately NO -DCURL_STATICLIB here: compat.curl publishes it itself, via a\n# `default` feature that implies one carrying the define. If that ever stops\n# working, reverts to dllimport declarations and the Windows link\n# fails — which is the point of not spelling it out on this side.\n[dependencies.compat]\ncurl = \"8.21.0\"\n", + "files": [ + { + "path": "tests/examples/curl/tests/transfer.cpp", + "text": "// Behavioral test — verify compat.curl builds a usable libcurl with working\n// TLS, without touching the network.\n//\n// CI runners have no reliable outbound network and the index's other members\n// never assume one, so nothing here opens a connection. What is asserted\n// instead is everything that a misconfigured build actually gets wrong:\n//\n// * the library initialises at all (curl_global_init walks the same setup\n// path a real transfer would),\n// * the TLS backend is really compiled in — a curl built with no SSL still\n// links and still runs, it just silently cannot do https, which is exactly\n// the failure EUI-NEO's `network` feature would hit at runtime,\n// * https and http are among the supported protocols,\n// * a handle can be configured with the options EUI-NEO actually sets.\n#include \nimport std;\n\n// CURL_STATICLIB must arrive from the PACKAGE, not from this project. Without\n// it declares everything __declspec(dllimport) and the Windows\n// link fails against a static archive; on other platforms it is inert, so this\n// compile-time check is the only thing that catches a regression early.\nstatic_assert([] {\n#if defined(CURL_STATICLIB)\n return true;\n#else\n return false;\n#endif\n}(), \"CURL_STATICLIB did not reach the consumer — compat.curl stopped publishing it\");\n\nint main() {\n if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {\n std::println(\"curl_global_init failed\");\n return 1;\n }\n\n const curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);\n if (info == nullptr) {\n std::println(\"curl_version_info returned null\");\n curl_global_cleanup();\n return 2;\n }\n\n // The whole point of wiring a TLS backend. `ssl_version` is null when curl\n // was built without one.\n if ((info->features & CURL_VERSION_SSL) == 0 || info->ssl_version == nullptr) {\n std::println(\"libcurl built WITHOUT TLS — https would fail at runtime\");\n curl_global_cleanup();\n return 3;\n }\n\n bool haveHttps = false;\n bool haveHttp = false;\n for (const char* const* p = info->protocols; p != nullptr && *p != nullptr; ++p) {\n const std::string_view proto{*p};\n if (proto == \"https\") haveHttps = true;\n if (proto == \"http\") haveHttp = true;\n }\n if (!haveHttp || !haveHttps) {\n std::println(\"missing protocol support (http={}, https={})\", haveHttp, haveHttps);\n curl_global_cleanup();\n return 4;\n }\n\n // The exact option set core/platform/network.cpp configures. A handle that\n // rejects any of these would break the `network` feature.\n CURL* handle = curl_easy_init();\n if (handle == nullptr) {\n std::println(\"curl_easy_init failed\");\n curl_global_cleanup();\n return 5;\n }\n const bool optionsOk =\n curl_easy_setopt(handle, CURLOPT_URL, \"https://example.invalid/\") == CURLE_OK &&\n curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L) == CURLE_OK &&\n curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15L) == CURLE_OK &&\n curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L) == CURLE_OK;\n curl_easy_cleanup(handle);\n\n if (!optionsOk) {\n std::println(\"curl_easy_setopt rejected an option EUI-NEO relies on\");\n curl_global_cleanup();\n return 6;\n }\n\n std::println(\"compat.curl: ok ({}, ssl={}, http+https present)\",\n info->version, info->ssl_version);\n curl_global_cleanup();\n return 0;\n}\n" + } + ] + }, + { + "project": "doctest", + "text": "# compat.doctest test project. The entry point comes from doctest's own\n# DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN macro rather than from a package feature —\n# that is the difference from tests/examples/catch2-main, where the framework\n# ships a compiled main. The `compat` redirect is inherited from the root.\n[package]\nname = \"doctest-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ndoctest = \"2.4.12\"\n", + "files": [ + { + "path": "tests/examples/doctest/tests/suite.cpp", + "text": "// Behavioral test — compat.doctest gives a working framework AND its main.\n//\n// No `import std;` here: doctest.h is a textual header that includes the C++\n// standard headers itself, and mixing those with the std module is the one\n// thing this index's module packages tell consumers not to do.\n//\n// A failing REQUIRE makes doctest return a non-zero exit code, which is how\n// `mcpp test` sees it — so the assertions below are the test, and the runner\n// needs no adapter.\n#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN\n#include \n\n#include \n#include \n\nstatic unsigned factorial(unsigned n) { return n <= 1 ? 1 : n * factorial(n - 1); }\n\nTEST_CASE(\"factorial\") {\n CHECK(factorial(0) == 1);\n CHECK(factorial(5) == 120);\n}\n\nTEST_CASE(\"subcases run independently\") {\n std::vector v;\n SUBCASE(\"one element\") {\n v.push_back(1);\n CHECK(v.size() == 1);\n }\n SUBCASE(\"two elements\") {\n v.push_back(1);\n v.push_back(2);\n CHECK(v.size() == 2);\n }\n // Each subcase re-enters with a fresh `v`; if doctest's subcase machinery\n // were missing, one of the two CHECKs above would see the other's push.\n}\n\nTEST_CASE(\"string comparison reporting\") {\n const std::string s = \"doctest\";\n REQUIRE(s.size() == 7);\n CHECK(s.substr(0, 3) == \"doc\");\n}\n" + } + ] + }, + { + "project": "egl", + "text": "# EGL test project.\n#\n# THIS MEMBER DECLARES ITS OWN [indices], and has to. The workspace root maps\n# the `compat` namespace to this checkout, and an index is looked up BY\n# NAMESPACE — so `freedesktop.*` would resolve against the published index\n# rather than the package under test. A member-level table REPLACES the root's\n# rather than merging.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"egl-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# Linux-only like its neighbours; the test compiles to a no-op main() elsewhere.\n[target.'cfg(linux)'.dependencies.freedesktop]\negl = \"1.7.0\"\n", + "files": [ + { + "path": "tests/examples/egl/tests/egl.cpp", + "text": "// freedesktop.egl — behavioral test, runnable with no GPU and no display.\n//\n// Every FUNCTION and TYPE below comes from `import khronos.egl;`. is\n// included for the EGL_* CONSTANTS only: they are macros, and no module can\n// export a macro. So the file compiling is itself the first assertion — the\n// module's export list has to cover everything used here — and the file\n// linking is the second.\n//\n// ─────────────────────────────────────────────────────────────────────────\n// EVERY ASSERTION HOLDS WITH ZERO VENDOR DRIVERS INSTALLED.\n//\n// libEGL is a DISPATCH: with no vendor it can answer questions about itself\n// and nothing else. Upstream returns the empty string outright when the vendor\n// list is empty (`libegl.c:928`), so an \"it lists some EGL_EXT_ extension\"\n// check measures whether the MACHINE has a driver, not whether this package\n// built libEGL. Vendor-dependent facts are reported below, or asserted only\n// once their precondition is visibly met.\n//\n// ─────────────────────────────────────────────────────────────────────────\n// THE PART THAT IS EASY TO GET FALSELY GREEN\n//\n// The ecosystem payload `xim:libglvnd` carries its own `libEGL.so.1` with the\n// SAME soname, and on a machine with the graphics stack installed it is\n// reachable. Only ONE library with a given soname is ever mapped, and nothing\n// warns about the other — so a test that merely calls EGL functions can pass\n// while this package's build is never loaded at all. Checks 2 and 3 pin the\n// identity from both directions: what the library says it is, and what path\n// its code actually came from. compat.libdrm's test does the same thing for\n// the same reason.\n\n#ifdef __linux__\n\n#include // the EGL_* macros only\n\n#include \n\n#include \n#include \n#include \n#include \n\nimport khronos.egl;\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. The module carries the API ────────────────────────────────────\n EGLDisplay none = EGL_NO_DISPLAY;\n check(none == EGL_NO_DISPLAY, \"EGLDisplay and EGL_NO_DISPLAY agree\");\n\n // ── 2. It is GLVND's libEGL ──────────────────────────────────────────\n // EGL_VERSION on EGL_NO_DISPLAY is answered by libglvnd before any vendor\n // is consulted — a literal \"1.5 libglvnd\" — so it needs no driver and no\n // display, and a different implementation would answer differently.\n const char *version = eglQueryString(EGL_NO_DISPLAY, EGL_VERSION);\n check(version != nullptr && std::strcmp(version, \"1.5 libglvnd\") == 0,\n \"eglQueryString(EGL_NO_DISPLAY, EGL_VERSION) is libglvnd's\");\n std::printf(\" EGL_VERSION: %s\\n\", version ? version : \"(null)\");\n\n // ── 3. …and it is THIS package's build, not the payload's ────────────\n // Same soname, so only one libEGL.so.1 is mapped and the loser is silent.\n // dladdr reports the object a symbol actually came from; if the path names\n // the ecosystem payload, this package built a library nobody loads.\n {\n Dl_info info{};\n const bool located =\n ::dladdr(reinterpret_cast(&eglQueryString), &info) != 0\n && info.dli_fname != nullptr;\n check(located, \"dladdr locates the loaded libEGL\");\n if (located) {\n const std::string from = info.dli_fname;\n std::printf(\" loaded from: %s\\n\", from.c_str());\n check(from.find(\"xim-x-libglvnd\") == std::string::npos,\n \"the loaded libEGL is not the ecosystem payload's copy\");\n }\n }\n\n // ── 4. The error path works, with no driver ──────────────────────────\n // EGL_VENDOR without a display is invalid; libglvnd must report\n // EGL_BAD_DISPLAY rather than crash or silently answer. This exercises\n // __eglReportError and the thread-local error state.\n check(eglQueryString(EGL_NO_DISPLAY, EGL_VENDOR) == nullptr,\n \"an invalid no-display query returns null\");\n check(eglGetError() == EGL_BAD_DISPLAY,\n \"…and leaves EGL_BAD_DISPLAY in the thread's error state\");\n\n // ── 5. libEGL reaches libGLdispatch ──────────────────────────────────\n // Resolving a CORE entry point runs the path from libEGL into\n // libGLdispatch. That library is built by a sibling member of the same\n // fork and reached by PATH rather than through the index — so this is also\n // the check that the intra-package edge survived publication.\n check(eglGetProcAddress(\"eglInitialize\") != nullptr,\n \"eglGetProcAddress(\\\"eglInitialize\\\") resolves\");\n check(eglGetProcAddress(\"eglNoSuchFunctionEXT\") == nullptr,\n \"…and a name that does not exist resolves to null\");\n\n // ── 6. What the environment supplies, reported ───────────────────────\n // The package compiles in an EMPTY vendor-config default on purpose, so a\n // driver is only ever found through `__EGL_VENDOR_LIBRARY_DIRS`, which\n // `xim:mesa` declares (xim-pkgindex#713) exactly as it declares\n // GBM_BACKENDS_PATH for compat.libgbm. A runner with no GPU stack is not a\n // defect in this package, so none of this is asserted — but when a vendor\n // IS found, the extension list must look like one.\n std::puts(\"\");\n const char *dirs = std::getenv(\"__EGL_VENDOR_LIBRARY_DIRS\");\n std::printf(\" __EGL_VENDOR_LIBRARY_DIRS = %s\\n\",\n dirs ? dirs : \"(unset — the ecosystem declares it)\");\n\n const char *client_exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);\n check(client_exts != nullptr,\n \"eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS) answers\");\n if (client_exts != nullptr && client_exts[0] != '\\0') {\n std::printf(\" client extensions: %.90s%s\\n\",\n client_exts, std::strlen(client_exts) > 90 ? \"…\" : \"\");\n check(std::strstr(client_exts, \"EGL_EXT_client_extensions\") != nullptr,\n \"a vendor was found, so the client extension list is populated\");\n } else {\n std::puts(\" client extensions: (none — no vendor driver here; the \"\n \"dispatch itself is fine)\");\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "eigen", + "text": "# Eigen test project: header-only linear algebra, asserted under `mcpp test`.\n# Resolves compat.eigen (source build puts the tree root on the include path)\n# from this repo's own index, via the inherited workspace-root `[indices]`.\n#\n# The dependency is spelled QUALIFIED on purpose. A bare `eigen = \"5.0.1\"` is\n# what most real consumers write, and it does resolve (0.0.109 takes both\n# halves of the wire address from the accepted descriptor, mcpp#286) — but the\n# index redirect is keyed by the REQUEST's namespace, and a bare request asks\n# under the default namespace, not `compat`. It would therefore resolve from\n# the published remote index and quietly stop testing this checkout, which is\n# the one thing this member exists to do. Bare-name resolution against a\n# short-name index is covered upstream by mcpp's e2e 165.\n[package]\nname = \"eigen-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\neigen = \"5.0.1\"\n", + "files": [ + { + "path": "tests/examples/eigen/tests/dense.cpp", + "text": "// Behavioral test: core (header-only) Eigen linear algebra. Returns non-zero on\n// any mismatch. (The eigen_blas feature's dgemm_ path is covered separately;\n// linking feature-built dependency objects into test binaries is a follow-up.)\n#include \n#include \n\nint main() {\n Eigen::Matrix2d A; A << 1, 2, 3, 4;\n Eigen::Vector2d x(1.0, 1.0);\n Eigen::Vector2d y = A * x; // [3,7]\n double det = A.determinant(); // -2\n Eigen::Vector2d z = A.colPivHouseholderQr().solve(y); // [1,1]\n bool ok = y(0) == 3.0 && y(1) == 7.0 && det == -2.0\n && std::abs(z(0) - 1.0) < 1e-9 && std::abs(z(1) - 1.0) < 1e-9;\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "eui-neo-app-main", + "text": "# compat.eui-neo, `app-main` FEATURE path: the package supplies `int main()`\n# (upstream core/app/glfw_app_main.cpp) and this project deliberately defines\n# none. If the feature stops contributing that source, this member fails to link\n# with `undefined reference to 'main'` — the same assertion shape as\n# tests/examples/catch2-main.\n#\n# This cannot share a project with tests/examples/eui-neo: features are resolved\n# per consuming project, and — because mcpp links a dependency's objects\n# EAGERLY rather than as lazily-selected archive members — a project that\n# enables `app-main` may not contain any translation unit of its own that\n# defines main(). See the note in pkgs/e/compat.eui-neo.lua.\n#\n# ALL THREE PLATFORMS, deliberately not cfg-gated. `app-main` selects upstream's\n# GLFW entry point, which is the default window backend on every platform, and\n# nothing in it is linux-specific — but until this member existed it was compiled\n# on NONE of them, so the windows `-fno-char8_t` / winmm+user32+pdh half of the\n# descriptor and the macOS Cocoa link were both unexercised for that TU.\n#\n# compat.glfw is NOT re-declared here even though this TU calls\n# glfwSetWindowShouldClose()/glfwGetCurrentContext() itself to bound the opt-in\n# window run: mcpp propagates a transitive dependency's `include_dirs` and link\n# inputs to the consumer, and compat.eui-neo already depends on compat.glfw on\n# every platform. Same convention as tests/examples/eui-neo-sdl2 and\n# eui-neo-vulkan, which include / without re-declaring\n# either.\n#\n# The `compat` index redirect is inherited from the workspace root.\n[package]\nname = \"eui-neo-app-main-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\neui-neo = { version = \"0.6.0\", features = [\"app-main\"] }\n", + "files": [ + { + "path": "tests/examples/eui-neo-app-main/tests/app_main.cpp", + "text": "// compat.eui-neo `app-main` FEATURE: there is no main() in this file on purpose.\n// The entry point is upstream's core/app/glfw_app_main.cpp, which the feature\n// adds to the library — it owns the window, the GL backend and the render loop,\n// and calls back into the two symbols an EUI application must supply:\n// app::dslAppConfig() and app::compose(). That is the whole shape being pinned\n// here, and it is the shape a real EUI-NEO application uses.\n//\n// tests/examples/eui-neo-window asserts the OTHER half of the same feature pair:\n// the same UI driven by a hand-written main(), with `app-main` NOT enabled.\n//\n// Two things make this member work under `mcpp test`:\n//\n// 1. The link IS the primary assertion. `main` has to come from the package;\n// if the feature ever stops gating glfw_app_main.cpp in, this member fails\n// with `undefined reference to 'main'`. Conversely, if the feature ever\n// stopped being opt-in, every OTHER member would fail with\n// `multiple definition of 'main'` — mcpp links a dependency's objects\n// eagerly, so there is no lazy-archive escape hatch.\n//\n// 2. Running a window needs a display, so the run is opt-in via\n// MCPP_RUN_WINDOW=1, as in tests/examples/imgui-window. Because the entry\n// point belongs to the package, the gate cannot live in main() — it lives in\n// a namespace-scope constructor, which runs before it. Headless CI links the\n// real binary, executes the guard, and exits 0.\n//\n// Builds on all three platforms: GLFW is upstream's default window backend\n// everywhere, and nothing below is platform-specific.\n#include \n\n#include \"core/render/render_backend.h\"\n\n// GLFW would otherwise pull a GL header of its own choosing (GL/gl.h on Windows,\n// the deprecated OpenGL/gl.h on macOS). EUI reaches GL through compat.glad, so\n// this TU wants the GLFW API and nothing else — the same thing\n// core/input/input_state.h does before its own include.\n#ifndef GLFW_INCLUDE_NONE\n#define GLFW_INCLUDE_NONE\n#endif\n#include \n\nimport std;\n\nnamespace {\n\n// Frames to render before asking the framework's loop to exit, so the opt-in run\n// terminates on its own instead of waiting for a human to close the window.\nconstexpr int kFrameBudget = 60;\n\nint g_frames = 0;\n\n// Accumulated from the PREVIOUS frame's render stats — the framework's loop\n// publishes them after each present(), and compose() is the only place an\n// app-main consumer gets to look. A frame counter alone would pass against a\n// null backend; these prove the OpenGL backend issued real geometry.\nlong long g_rectDraws = 0;\nlong long g_textDraws = 0;\n\n// Runs before the package's main(). See point 2 above.\nstruct WindowRunGate {\n WindowRunGate() {\n if (std::getenv(\"MCPP_RUN_WINDOW\") != nullptr) {\n return;\n }\n std::println(\"compat.eui-neo[app-main]: linked, main() supplied by the package; \"\n \"window run is opt-in (MCPP_RUN_WINDOW=1)\");\n std::exit(0);\n }\n};\n\nconst WindowRunGate g_gate;\n\n} // namespace\n\nnamespace app {\n\nconst DslAppConfig& dslAppConfig() {\n static const DslAppConfig config = DslAppConfig{}\n .title(\"compat.eui-neo — app-main\")\n .pageId(\"eui_neo_app_main\")\n .clearColor({0.06f, 0.07f, 0.09f, 1.0f})\n .windowSize(420, 240)\n .fps(60.0)\n .showDebugStatsInTitle(false);\n return config;\n}\n\nvoid compose(eui::Ui& ui, const eui::Screen& screen) {\n // A node carrying .onFrame() marks the runtime as animating, which is what\n // keeps the framework's loop rendering continuously instead of parking in\n // glfwWaitEvents(). It is also the only per-frame hook an app-main consumer\n // has, so the frame budget is enforced from here.\n ui.stack(\"loop.driver\")\n .size(1.0f, 1.0f)\n .onFrame([](float deltaSeconds) {\n // app::update() runs the runtime twice per frame — the second pass\n // with deltaSeconds == 0, to settle the re-compose that onFrame\n // itself requested — so this fires twice per rendered frame.\n if (deltaSeconds <= 0.0f) {\n return;\n }\n\n const core::render::RenderFrameStats& stats = core::render::lastRenderFrameStats();\n g_rectDraws += stats.rectDraws;\n g_textDraws += stats.textDraws;\n\n if (++g_frames < kFrameBudget) {\n return;\n }\n\n // The entry point belongs to the package, so there is no main() of\n // ours to return a code from — exit() is how this member fails.\n if (g_rectDraws <= 0) {\n std::println(\"no rect geometry reached the backend over {} frames\", g_frames);\n std::exit(2);\n }\n if (g_textDraws <= 0) {\n // Text needs the freetype path AND a usable font; worth\n // distinguishing from a dead backend.\n std::println(\"no text geometry reached the backend over {} frames \"\n \"(freetype path or system font missing)\", g_frames);\n std::exit(3);\n }\n\n std::println(\"compat.eui-neo[app-main]: ok ({} frames, {} rect / {} text draws)\",\n g_frames, g_rectDraws, g_textDraws);\n\n // The framework's loop keeps our window's context current across\n // update and render, so this is the window it is driving.\n if (GLFWwindow* window = glfwGetCurrentContext()) {\n glfwSetWindowShouldClose(window, GLFW_TRUE);\n }\n })\n .build();\n\n // Text exercises the freetype + font-fallback path, rects the OpenGL\n // primitive path — i.e. the parts a headless smoke test cannot reach.\n ui.text(\"title\")\n .position(32.0f, 40.0f)\n .size(std::max(120.0f, screen.width - 64.0f), 30.0f)\n .text(\"app-main\")\n .fontSize(22.0f)\n .lineHeight(28.0f)\n .color({0.94f, 0.96f, 1.0f, 1.0f})\n .build();\n\n ui.text(\"stats\")\n .position(32.0f, 76.0f)\n .size(std::max(120.0f, screen.width - 64.0f), 24.0f)\n .text(std::format(\"frame {} / {}\", g_frames, kFrameBudget))\n .fontSize(15.0f)\n .lineHeight(20.0f)\n .color({0.58f, 0.68f, 0.80f, 1.0f})\n .build();\n\n const float progress = static_cast(g_frames) / static_cast(kFrameBudget);\n const float trackWidth = std::max(120.0f, screen.width - 64.0f);\n\n ui.rect(\"track\")\n .position(32.0f, 150.0f)\n .size(trackWidth, 6.0f)\n .radius(3.0f)\n .color({0.16f, 0.19f, 0.24f, 1.0f})\n .build();\n\n ui.rect(\"fill\")\n .position(32.0f, 150.0f)\n .size(std::min(1.0f, progress) * trackWidth, 6.0f)\n .radius(3.0f)\n .color({0.36f, 0.62f, 0.98f, 1.0f})\n .build();\n}\n\n} // namespace app\n" + } + ] + }, + { + "project": "eui-neo-markdown", + "text": "[package]\nname = \"eui-neo-markdown-tests\"\nversion = \"0.1.0\"\n\n# Long-form declaration so the `markdown` feature is requested. It pulls\n# compat.md4c and publishes EUI_HAS_MD4C=1 as an interface define, which is\n# what switches components/markdown.h from its fallback to the real parser.\n\n[dependencies.compat]\neui-neo = { version = \"0.6.0\", features = [\"markdown\"] }\n", + "files": [ + { + "path": "tests/examples/eui-neo-markdown/tests/markdown.cpp", + "text": "// POSITIVE verification for compat.eui-neo's `markdown` feature.\n//\n// components/markdown.h is header-only and compiles one of two definitions of\n// detail::parseMarkdownBlocks depending on EUI_HAS_MD4C. That makes it a clean\n// probe for something the descriptor cannot otherwise prove: the feature's\n// `defines` really do reach the CONSUMER's translation unit, not just the\n// package's own. Without propagation this member would link md4c and still\n// silently get the fallback parser.\n//\n// tests/examples/eui-neo asserts the negative side — same header, feature off,\n// degenerate single-Paragraph result.\n#include \nimport std;\n\nnamespace app {\n\nconst DslAppConfig& dslAppConfig() {\n static const DslAppConfig config = DslAppConfig{}.title(\"markdown feature test\");\n return config;\n}\n\nvoid compose(eui::Ui&, const eui::Screen&) {}\n\n} // namespace app\n\nint main() {\n#if !defined(EUI_HAS_MD4C)\n std::println(\"EUI_HAS_MD4C not defined — the feature's interface define did not propagate\");\n return 1;\n#else\n namespace md = components::detail;\n\n const auto blocks = md::parseMarkdownBlocks(\"# Heading\\n\\nBody text.\\n\");\n\n // The real parser splits heading from paragraph; the fallback returns one\n // Paragraph holding the raw source.\n if (blocks.size() < 2) {\n std::println(\"expected >= 2 blocks from the md4c parser, got {}\", blocks.size());\n return 2;\n }\n if (blocks[0].kind != md::MarkdownBlockKind::Heading || blocks[0].headingLevel != 1) {\n std::println(\"expected an h1 first block, got kind={} level={}\",\n static_cast(blocks[0].kind), blocks[0].headingLevel);\n return 3;\n }\n if (md::plainText(blocks[0].runs) != \"Heading\") {\n std::println(\"unexpected heading text: '{}'\", md::plainText(blocks[0].runs));\n return 4;\n }\n\n std::println(\"compat.eui-neo[markdown]: ok ({} blocks, h1 = '{}')\",\n blocks.size(), md::plainText(blocks[0].runs));\n return 0;\n#endif\n}\n" + } + ] + }, + { + "project": "eui-neo-sdl2", + "text": "[package]\nname = \"eui-neo-sdl2-tests\"\nversion = \"0.1.0\"\n\n# Covers the two remaining alternates in one member: the SDL2 window backend\n# replacing GLFW, and `network` turning core/platform/network.cpp from stubs\n# into a real libcurl-backed implementation. The render backend stays OpenGL,\n# unnamed — naming a feature no longer costs you the defaults.\n[dependencies.compat]\neui-neo = { version = \"0.6.0\", features = [\"sdl2\", \"network\"] }\n", + "files": [ + { + "path": "tests/examples/eui-neo-sdl2/tests/backends.cpp", + "text": "// Verify compat.eui-neo's `sdl2` window backend and `network` feature.\n//\n// Both are asserted at COMPILE time first, because both fail silently at\n// runtime otherwise: a window backend that did not switch just uses GLFW, and\n// `network` that did not switch leaves core/platform/network.cpp compiled as\n// stubs that return failure. Neither shows up without a display or a network.\n#include \n// SDL_MAIN_HANDLED before : on Windows SDL_main.h does\n// `#define main SDL_main` and expects the real entry point to come from\n// SDL2main (src/main/windows/SDL_windows_main.c). This package does not ship\n// that — a library consumer should not be handed an entry point — so the test\n// takes SDL's documented alternative and keeps its own main, pairing it with\n// SDL_SetMainReady() below. Without this the link fails with\n// \"LNK1561: entry point must be defined\".\n#define SDL_MAIN_HANDLED\n#include \n#include \nimport std;\n\n#if !defined(EUI_WINDOW_BACKEND_SDL2)\n#error \"sdl2 feature requested but EUI_WINDOW_BACKEND_SDL2 is not defined\"\n#endif\n#if !defined(EUI_HAS_CURL)\n#error \"network feature requested but EUI_HAS_CURL is not defined\"\n#endif\n\nnamespace app {\n\nconst DslAppConfig& dslAppConfig() {\n static const DslAppConfig config = DslAppConfig{}.title(\"sdl2 + network test\");\n return config;\n}\n\nvoid compose(eui::Ui&, const eui::Screen&) {}\n\n} // namespace app\n\nint main() {\n // SDL's dummy driver is a real driver, so unlike the GL/Vulkan backends\n // this actually runs: init, create, query, tear down.\n SDL_SetMainReady();\n SDL_SetHint(SDL_HINT_VIDEODRIVER, \"dummy\");\n if (SDL_Init(SDL_INIT_VIDEO) != 0) {\n std::println(\"SDL_Init failed: {}\", SDL_GetError());\n return 1;\n }\n SDL_Window* window = SDL_CreateWindow(\"eui-neo\", SDL_WINDOWPOS_UNDEFINED,\n SDL_WINDOWPOS_UNDEFINED, 200, 100,\n SDL_WINDOW_HIDDEN);\n if (window == nullptr) {\n std::println(\"SDL_CreateWindow failed: {}\", SDL_GetError());\n SDL_Quit();\n return 2;\n }\n const char* driver = SDL_GetCurrentVideoDriver();\n SDL_DestroyWindow(window);\n SDL_Quit();\n\n // libcurl came in through the feature's dependency, and it has TLS — the\n // thing EUI-NEO's downloader needs and the thing a misconfigured curl\n // silently lacks. No connection is made.\n if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {\n std::println(\"curl_global_init failed\");\n return 3;\n }\n const curl_version_info_data* curlInfo = curl_version_info(CURLVERSION_NOW);\n const bool haveTls = curlInfo != nullptr &&\n (curlInfo->features & CURL_VERSION_SSL) != 0 &&\n curlInfo->ssl_version != nullptr;\n curl_global_cleanup();\n if (!haveTls) {\n std::println(\"libcurl reached us without TLS\");\n return 4;\n }\n\n // Still eui-neo underneath — a headless facade from the base source set.\n eui::json::Document doc;\n if (!doc.parse(R\"({\"window\": \"sdl2\"})\")) {\n std::println(\"parse failed: {}\", doc.error().message);\n return 5;\n }\n std::string windowBackend;\n if (!doc.stringAt(\"/window\", windowBackend) || windowBackend != \"sdl2\") {\n std::println(\"unexpected /window: '{}'\", windowBackend);\n return 6;\n }\n\n std::println(\"compat.eui-neo[sdl2,network]: ok (SDL driver={}, curl {} ssl={})\",\n driver, curlInfo->version, curlInfo->ssl_version);\n return 0;\n}\n" + } + ] + }, + { + "project": "eui-neo-tray", + "text": "# compat.eui-neo's system tray, over the public `core::platform` surface.\n#\n# The other six eui-neo members cover the headless surface, the two entry-point\n# shapes, and the SDL2 / Vulkan / markdown legs. None of them touches the tray,\n# and none of them CAN detect it: tray_bridge.c's `#else` arm defines all six\n# tray symbols and returns 0 from each, so a descriptor that lost its backend\n# define keeps every existing member green while the tray quietly stops\n# existing. This member exists to make that difference observable.\n#\n# ALL THREE PLATFORMS, deliberately not cfg-gated. The descriptor gives windows\n# EUI_TRAY_WINAPI and macOS EUI_TRAY_APPKIT unconditionally, and linux\n# EUI_TRAY_SNI since 0.5.7; `core/platform/platform.h` is platform-clean, so the\n# consumer code below is identical everywhere. Only the ASSERTION is\n# linux-specific, for the reason given in tests/tray.cpp.\n#\n# compat.eui-neo is the only declared dependency. On linux the tray pulls glib\n# through the package's own staged copy and its `runtime` deps, all of which\n# mcpp propagates to the consumer -- the same convention as eui-neo-sdl2 and\n# eui-neo-vulkan, which include / without re-declaring\n# either.\n#\n# The `compat` index redirect is inherited from the workspace root.\n[package]\nname = \"eui-neo-tray-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\neui-neo = \"0.6.0\"\n", + "files": [ + { + "path": "tests/examples/eui-neo-tray/tests/tray.cpp", + "text": "// compat.eui-neo's SYSTEM TRAY, through the public C++ surface.\n//\n// This is the shape a consumer should copy. `core::platform` exposes the whole\n// tray as six free functions and a two-field options struct, and none of it\n// needs a window, a GL context, or the DSL app loop -- a tray-only utility is\n// the ten lines in runTray() below. The raw C bridge in\n// core/platform/tray_bridge.h is an implementation detail and is not used here.\n//\n// WHY THIS MEMBER EXISTS, and why the assertion is the shape it is.\n//\n// tray_bridge.c selects one of four backends at preprocessor level --\n// EUI_TRAY_WINAPI, EUI_TRAY_APPKIT, EUI_TRAY_SNI, EUI_TRAY_APPINDICATOR -- and\n// falls through to an `#else` stub when none is defined. THE STUB DEFINES ALL\n// SIX SYMBOLS and returns 0 from every one of them. A descriptor that lost its\n// backend define would still compile, still link, and still pass all six other\n// eui-neo members, while the tray silently stopped existing: \"no tray\" and \"not\n// tested\" would read identically. That is the failure this member is built\n// against.\n//\n// Two criteria were tried and only the second one works:\n//\n// * `dlsym(RTLD_DEFAULT, \"g_bus_get_sync\") != nullptr` -- BLUNT, verified by\n// rebuilding with the define removed and watching it pass anyway. The\n// descriptor's `ldflags` carry `-lgio-2.0` unconditionally, so gio is in\n// DT_NEEDED and its symbols resolve whether or not the SNI arm was\n// compiled. It answered \"is gio linked\", not \"is the backend real\".\n//\n// * initializeTray() under a session bus -- SHARP. Measured both ways on a\n// bare `dbus-run-session` with no panel and no display: the real backend\n// returns true (it exports its StatusNotifierItem object and waits for a\n// watcher), the stub returns false unconditionally.\n//\n// So on linux this test supplies its own private bus and asserts the tray comes\n// up. It needs no display, no panel, and no desktop session, which is what lets\n// it run on an ordinary CI runner.\n//\n// Windows and macOS get compile-and-link coverage only. Their backends are\n// unconditional in the descriptor, and neither has an equivalent cheap probe\n// that a headless runner can perform.\n\n#include \"core/platform/platform.h\"\n\n#if defined(__linux__)\n#include \n#include \n#endif\n\nimport std;\n\nnamespace {\n\n// What a consumer actually writes. Everything else in this file is about\n// proving the backend is real; this is the feature.\nint runTray() {\n core::platform::TrayOptions options;\n options.tooltip = \"EUI NEO tray example\";\n options.iconPath = \"\"; // empty: the panel substitutes a placeholder icon\n\n if (!core::platform::initializeTray(options)) {\n std::println(\"tray unavailable (no session bus)\");\n return 1;\n }\n\n std::println(\"tray is live -- right click it for Show / Exit\");\n while (!core::platform::consumeTrayExitRequested()) {\n core::platform::pollTray(false);\n if (core::platform::consumeTrayShowRequested()) std::println(\" -> Show\");\n std::this_thread::sleep_for(std::chrono::milliseconds(50));\n }\n\n core::platform::shutdownTray();\n std::println(\"tray shut down\");\n return 0;\n}\n\n#if defined(__linux__)\n// Re-run this same executable with a private session bus around it. Returns\n// only on failure; on success `dbus-run-session` replaces this process and the\n// second entry takes the branch below it.\nvoid reexec_under_private_bus(const char* marker) {\n ::setenv(marker, \"1\", 1);\n\n char self[PATH_MAX];\n const ssize_t n = ::readlink(\"/proc/self/exe\", self, sizeof(self) - 1);\n if (n <= 0) return;\n self[n] = '\\0';\n\n char* const argv[] = { const_cast(\"dbus-run-session\"),\n const_cast(\"--\"), self, nullptr };\n ::execvp(\"dbus-run-session\", argv);\n}\n#endif\n\n} // namespace\n\nint main() {\n // Safe on every platform and before any backend has been touched: the state\n // is a plain bool and the stub's eui_tray_is_initialized() also returns 0.\n // This pins the query, not the backend.\n if (core::platform::isTrayInitialized()) {\n std::println(stderr, \"isTrayInitialized() is true before initializeTray()\");\n return 1;\n }\n\n if (std::getenv(\"MCPP_RUN_TRAY\") != nullptr) return runTray();\n\n#if defined(__linux__)\n constexpr const char* kMarker = \"EUI_TRAY_TEST_HAS_BUS\";\n\n if (std::getenv(\"DBUS_SESSION_BUS_ADDRESS\") == nullptr) {\n if (std::getenv(kMarker) != nullptr) {\n // dbus-run-session ran but produced no bus address. Do not report\n // this as a pass: an unverifiable backend is exactly what this\n // member exists to prevent.\n std::println(stderr,\n \"dbus-run-session left no DBUS_SESSION_BUS_ADDRESS, so \"\n \"the linux tray backend could not be verified\");\n return 1;\n }\n reexec_under_private_bus(kMarker);\n std::println(stderr,\n \"could not exec dbus-run-session, so the linux tray backend \"\n \"could not be verified. It ships with dbus; install that \"\n \"package on this runner.\");\n return 1;\n }\n\n // THE ASSERTION. True only when tray_bridge.c compiled a real backend --\n // the EUI_TRAY_HAS_BACKEND=0 stub returns 0 here no matter what the bus is.\n core::platform::TrayOptions options;\n options.tooltip = \"compat.eui-neo tray smoke test\";\n if (!core::platform::initializeTray(options)) {\n std::println(stderr,\n \"compat.eui-neo was built WITHOUT its linux tray backend: a \"\n \"session bus is present, yet initializeTray() failed, which \"\n \"is what tray_bridge.c's EUI_TRAY_HAS_BACKEND=0 stub does \"\n \"unconditionally. Expected -DEUI_TRAY_SNI=1 and glib on the \"\n \"link line (see pkgs/e/compat.eui-neo.lua).\");\n return 1;\n }\n\n if (!core::platform::isTrayInitialized()) {\n std::println(stderr, \"initializeTray() succeeded but isTrayInitialized() is false\");\n return 1;\n }\n core::platform::shutdownTray();\n std::println(\"linux tray backend verified over a private session bus\");\n#else\n std::println(\"tray path compiled and linked; run with MCPP_RUN_TRAY=1 to show it\");\n#endif\n return 0;\n}\n" + } + ] + }, + { + "project": "eui-neo-vulkan", + "text": "[package]\nname = \"eui-neo-vulkan-tests\"\nversion = \"0.1.0\"\n\n# `vulkan` REPLACES the default OpenGL backend rather than adding to it: the\n# package resolves the exclusive choice in its own preprocessor from the\n# MCPP_FEATURE_* flags. The window backend stays GLFW, unnamed.\n[dependencies.compat]\neui-neo = { version = \"0.6.0\", features = [\"vulkan\"] }\n\n[build]\ncxxflags = [\"-DHAVE_EUI_VULKAN=1\"]\n", + "files": [ + { + "path": "tests/examples/eui-neo-vulkan/tests/backend.cpp", + "text": "// Verify compat.eui-neo's `vulkan` feature actually swaps the render backend.\n//\n// Two things have to hold, and only one of them is about Vulkan:\n//\n// 1. The backend define really flipped. EUI_RENDER_BACKEND_VULKAN must be\n// visible here and EUI_RENDER_BACKEND_OPENGL must NOT — if `default` had\n// leaked through alongside the explicit feature, both would be set and\n// core/render/render_backend.cpp would have silently compiled the OpenGL\n// path (its dispatch is `#if OPENGL … #elif VULKAN`, so OpenGL wins).\n// That failure is invisible at runtime without a GPU, so it is asserted at\n// COMPILE time below.\n//\n// 2. The Vulkan translation units are linked. compat.vulkan's loader entry\n// points must resolve from a consumer that reached them through eui-neo's\n// feature-scoped dependency, not through a direct one.\n//\n// Everything else about the Vulkan backend needs a device, a surface and a\n// window, none of which exist on a CI runner — see the design doc.\n//\n// HAVE_EUI_VULKAN is set by THIS project's own [target.'cfg(...)'.build]\n// cxxflags: compat.vulkan has no windows build, so on windows the member takes\n// the default OpenGL configuration instead and asserts THAT.\n#include \n#if defined(HAVE_EUI_VULKAN)\n#include \n#endif\nimport std;\n\n#if defined(HAVE_EUI_VULKAN)\n# if !defined(EUI_RENDER_BACKEND_VULKAN)\n# error \"vulkan feature requested but EUI_RENDER_BACKEND_VULKAN is not defined\"\n# endif\n# if defined(EUI_RENDER_BACKEND_OPENGL)\n# error \"EUI_RENDER_BACKEND_OPENGL leaked in alongside the vulkan feature\"\n# endif\n#else\n// The default (OpenGL) build publishes NO interface define — the package\n// resolves it in its own preprocessor, which the consumer never sees. So the\n// only thing assertable from here is the absence of the vulkan one.\n# if defined(EUI_RENDER_BACKEND_VULKAN)\n# error \"EUI_RENDER_BACKEND_VULKAN present without the vulkan feature\"\n# endif\n#endif\n\nnamespace app {\n\nconst DslAppConfig& dslAppConfig() {\n static const DslAppConfig config = DslAppConfig{}.title(\"vulkan backend test\");\n return config;\n}\n\nvoid compose(eui::Ui&, const eui::Screen&) {}\n\n} // namespace app\n\nint main() {\n // The loader answers this without any ICD, so it is safe on a driverless\n // runner while still proving compat.vulkan came along with the feature.\n#if defined(HAVE_EUI_VULKAN)\n std::uint32_t apiVersion = 0;\n if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) {\n std::println(\"vkEnumerateInstanceVersion failed\");\n return 1;\n }\n#endif\n\n // Still the eui-neo library underneath: assert on a headless facade that\n // lives in core/platform/json.cpp, so a build that dropped the base\n // sources while chasing the feature fails here.\n eui::json::Document doc;\n if (!doc.parse(R\"({\"backend\": \"vulkan\"})\")) {\n std::println(\"parse failed: {}\", doc.error().message);\n return 2;\n }\n std::string backend;\n if (!doc.stringAt(\"/backend\", backend) || backend != \"vulkan\") {\n std::println(\"unexpected /backend: '{}'\", backend);\n return 3;\n }\n\n#if defined(HAVE_EUI_VULKAN)\n std::println(\"compat.eui-neo[vulkan]: ok (backend={}, loader api {}.{}.{})\",\n backend, VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion),\n VK_VERSION_PATCH(apiVersion));\n#else\n std::println(\"compat.eui-neo[vulkan]: skipped, default opengl build asserted instead (parsed {})\",\n backend);\n#endif\n return 0;\n}\n" + } + ] + }, + { + "project": "eui-neo-window", + "text": "# compat.eui-neo driven by a HAND-WRITTEN main() — `app-main` deliberately NOT\n# enabled, so the package contributes no entry point and this project owns the\n# window, the render backend and the loop.\n#\n# The pair matters: tests/examples/eui-neo-app-main asserts the framework-main\n# shape, this member asserts the own-main shape. Nothing else in this index\n# builds an eui-neo WINDOW at all — tests/examples/eui-neo is headless by\n# construction (json + platform flags), so window creation, the OpenGL backend,\n# the text/freetype path and the render loop were previously uncovered.\n#\n# ALL THREE PLATFORMS, deliberately not cfg-gated. Unlike tests/examples/\n# imgui-window and gui-stack — linux-only because they pull the X11 packages\n# directly — compat.eui-neo and compat.glfw both build on linux/macosx/windows,\n# and the four existing eui-neo members already prove that on every runner. What\n# was never compiled anywhere is a CONSUMER translation unit that includes\n# core/window, core/render and , which is exactly what this member\n# is. None of the headers it reaches carry platform-specific includes.\n#\n# The window run needs a display and is opt-in (MCPP_RUN_WINDOW=1); headless CI\n# compiles + links the whole loop on each OS and returns 0.\n#\n# compat.glfw is NOT re-declared here even though this TU calls glfwPollEvents()\n# and glfwWindowShouldClose() itself (EUI's core::window facade exposes no\n# event-pump entry point). mcpp propagates a transitive dependency's\n# `include_dirs` and link inputs to the consumer, so the edge through\n# compat.eui-neo is enough — verified by dropping the line and rebuilding. Same\n# convention as tests/examples/eui-neo-sdl2 (`#include `, ``)\n# and eui-neo-vulkan (`vkEnumerateInstanceVersion`), neither of which re-declares\n# what it reaches through a feature.\n#\n# The `compat` index redirect is inherited from the workspace root.\n[package]\nname = \"eui-neo-window-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\neui-neo = \"0.6.0\"\n", + "files": [ + { + "path": "tests/examples/eui-neo-window/tests/window.cpp", + "text": "// compat.eui-neo with a HAND-WRITTEN main(): the own-main shape, and the only\n// place in this index that builds an eui-neo window.\n//\n// tests/examples/eui-neo covers the headless surface (json, platform flags) and\n// tests/examples/eui-neo-app-main covers the framework-main shape. What is left,\n// and what this member pins, is everything between window creation and a\n// presented frame: core::window::createWindow(), the OpenGL render backend,\n// app::initialize/update/render, ScopedRenderBackend, and the freetype text path.\n//\n// Without `app-main` the package contributes no entry point, so the five things\n// upstream's glfw_app_main.cpp does have to be done by hand — see runWindow().\n// That is the point: a consumer must be able to drive EUI itself, and the public\n// surface it needs (core::window, core::render, app::*) must actually be reachable\n// through the descriptor's include_dirs.\n//\n// The window run needs a display, so it is opt-in via MCPP_RUN_WINDOW=1, exactly\n// as in tests/examples/imgui-window. Headless CI compiles and links the whole\n// loop — which is where a descriptor regression would surface — and returns 0.\n//\n// Builds on all three platforms. Every core/ header below is platform-clean (no\n// windows.h, no Cocoa, no GL), and every GLFW entry point used here is portable,\n// so there is no reason to cfg-gate this the way the X11-consuming members are.\n#include \n\n// 0.5.6 dropped `eui/detail/dsl_app_impl.h` from the umbrella `eui_neo.h`\n// (upstream scoped it to the app-main entry points, which include it directly).\n// This member is the OWN-main shape — nothing in the package drives the loop —\n// so app::initialize/update/render/shutdown are emitted HERE by including the\n// impl header, exactly as upstream's core/app/glfw_app_main.cpp does.\n#include \"eui/detail/dsl_app_impl.h\"\n\n#include \"core/input/input_state.h\"\n#include \"core/platform/platform.h\"\n#include \"core/render/render_backend.h\"\n#include \"core/window/window_backend.h\"\n\n// GLFW would otherwise pull a GL header of its own choosing (GL/gl.h on Windows,\n// the deprecated OpenGL/gl.h on macOS). EUI reaches GL through compat.glad, so\n// this TU wants the GLFW API and nothing else — the same thing\n// core/input/input_state.h does before its own include.\n#ifndef GLFW_INCLUDE_NONE\n#define GLFW_INCLUDE_NONE\n#endif\n#include \n\nimport std;\n\nnamespace {\n\n// Rendered frames to require before exiting, so the opt-in run terminates on its\n// own. Also the assertion: fewer than this means the loop stalled.\nconstexpr int kFrameBudget = 60;\n\nstruct Loop {\n float elapsed = 0.0f;\n int frames = 0;\n // Accumulated from core::render::lastRenderFrameStats(). A frame counter only\n // proves app::update() ran — these prove the OpenGL backend actually issued\n // geometry, i.e. that the render half is wired too. Without them this member\n // would pass just as happily against a null backend.\n long long rectDraws = 0;\n long long textDraws = 0;\n};\n\nLoop g_loop;\n\n} // namespace\n\nnamespace app {\n\nconst DslAppConfig& dslAppConfig() {\n static const DslAppConfig config = DslAppConfig{}\n .title(\"compat.eui-neo — own main\")\n .pageId(\"eui_neo_own_main\")\n .clearColor({0.06f, 0.07f, 0.09f, 1.0f})\n .windowSize(420, 240)\n .fps(60.0)\n .showDebugStatsInTitle(false);\n return config;\n}\n\nvoid compose(eui::Ui& ui, const eui::Screen& screen) {\n // A node carrying .onFrame() marks the runtime as animating, which is what\n // turns a redraw-on-demand retained-mode UI into a continuously running loop.\n ui.stack(\"loop.driver\")\n .size(1.0f, 1.0f)\n .onFrame([](float deltaSeconds) {\n // app::update() runs the runtime twice per frame — the second pass\n // with deltaSeconds == 0, to settle the re-compose that onFrame\n // itself requested — so this fires twice per rendered frame.\n if (deltaSeconds <= 0.0f) {\n return;\n }\n g_loop.elapsed += deltaSeconds;\n ++g_loop.frames;\n })\n .build();\n\n // Text exercises the freetype + font-fallback path, rects the OpenGL\n // primitive path.\n const float trackWidth = std::max(120.0f, screen.width - 64.0f);\n\n ui.text(\"title\")\n .position(32.0f, 40.0f)\n .size(trackWidth, 30.0f)\n .text(\"own main\")\n .fontSize(22.0f)\n .lineHeight(28.0f)\n .color({0.94f, 0.96f, 1.0f, 1.0f})\n .build();\n\n ui.text(\"stats\")\n .position(32.0f, 76.0f)\n .size(trackWidth, 24.0f)\n .text(std::format(\"frame {} / {} t = {:.2f}s\", g_loop.frames, kFrameBudget, g_loop.elapsed))\n .fontSize(15.0f)\n .lineHeight(20.0f)\n .color({0.58f, 0.68f, 0.80f, 1.0f})\n .build();\n\n const float progress = static_cast(g_loop.frames) / static_cast(kFrameBudget);\n\n ui.rect(\"track\")\n .position(32.0f, 150.0f)\n .size(trackWidth, 6.0f)\n .radius(3.0f)\n .color({0.16f, 0.19f, 0.24f, 1.0f})\n .build();\n\n ui.rect(\"fill\")\n .position(32.0f, 150.0f)\n .size(std::min(1.0f, progress) * trackWidth, 6.0f)\n .radius(3.0f)\n .color({0.98f, 0.55f, 0.36f, 1.0f})\n .build();\n}\n\n} // namespace app\n\nnamespace {\n\nfloat dpiScaleOf(GLFWwindow* window) {\n float scaleX = 1.0f;\n float scaleY = 1.0f;\n glfwGetWindowContentScale(window, &scaleX, &scaleY);\n return (scaleX + scaleY) * 0.5f;\n}\n\n// Framebuffer pixels per window point; EUI needs it to map cursor coordinates\n// onto the framebuffer.\nfloat pointerScaleOf(GLFWwindow* window) {\n int windowWidth = 0;\n int windowHeight = 0;\n int framebufferWidth = 0;\n int framebufferHeight = 0;\n glfwGetWindowSize(window, &windowWidth, &windowHeight);\n glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight);\n if (windowWidth <= 0 || windowHeight <= 0) {\n return 1.0f;\n }\n return 0.5f * (static_cast(framebufferWidth) / static_cast(windowWidth) +\n static_cast(framebufferHeight) / static_cast(windowHeight));\n}\n\nint runWindow() {\n core::platform::repairCurrentWorkingDirectory();\n core::render::initializeRenderBackendLoader();\n\n glfwSetErrorCallback([](int code, const char* description) {\n std::println(\"glfw error {}: {}\", code, description != nullptr ? description : \"\");\n });\n\n if (!glfwInit()) {\n std::println(\"glfwInit() failed\");\n return 10;\n }\n\n const app::DslAppConfig& config = app::dslAppConfig();\n\n core::window::WindowCreateRequest request;\n request.width = config.windowWidthValue;\n request.height = config.windowHeightValue;\n request.title = config.titleValue.c_str();\n request.renderApi = core::render::windowRenderApi();\n\n auto* window = static_cast(core::window::createWindow(request));\n if (window == nullptr) {\n std::println(\"core::window::createWindow() failed\");\n glfwTerminate();\n return 11;\n }\n\n auto backend = core::render::createRenderBackend(window);\n if (!backend || !backend->initialize()) {\n std::println(\"core::render::createRenderBackend() failed\");\n core::window::destroyWindow(window);\n glfwTerminate();\n return 12;\n }\n\n if (!app::initialize(window)) {\n std::println(\"app::initialize() failed\");\n backend.reset();\n core::window::destroyWindow(window);\n glfwTerminate();\n return 13;\n }\n\n // Frame pacing is NOT optional in a hand-written loop: the OpenGL backend\n // calls glfwSwapInterval(0) in initialize(), so present() never blocks on\n // vsync and an unpaced loop runs at thousands of fps and pins a core.\n // Upstream's main does the same thing with monitor refresh-rate detection on\n // top.\n const double frameInterval = config.fpsValue > 0.0 ? 1.0 / config.fpsValue : 0.0;\n double lastFrameTime = core::window::timeSeconds();\n double nextFrameTime = lastFrameTime;\n\n while (!glfwWindowShouldClose(window) && g_loop.frames < kFrameBudget) {\n glfwPollEvents();\n\n const double now = core::window::timeSeconds();\n const float deltaSeconds = static_cast(now - lastFrameTime);\n lastFrameTime = now;\n\n int framebufferWidth = 0;\n int framebufferHeight = 0;\n glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight);\n if (framebufferWidth <= 0 || framebufferHeight <= 0) {\n glfwWaitEvents();\n lastFrameTime = core::window::timeSeconds();\n nextFrameTime = lastFrameTime;\n continue;\n }\n\n const float dpiScale = dpiScaleOf(window);\n const float pointerScale = pointerScaleOf(window);\n\n backend->makeCurrent();\n\n // Layout + compose() + input dispatch; also fires the .onFrame()\n // callback that advances g_loop.\n app::update(window, deltaSeconds, framebufferWidth, framebufferHeight, dpiScale, pointerScale);\n\n backend->beginFrame({\n window,\n core::window::nativeWindowInfo(window),\n framebufferWidth,\n framebufferHeight,\n dpiScale\n });\n {\n // app::render() draws through core::render::activeRenderBackend();\n // this guard is what installs ours as the active one. Without it the\n // frame is silently dropped.\n core::render::ScopedRenderBackend scopedBackend(*backend);\n app::render(framebufferWidth, framebufferHeight, dpiScale);\n }\n backend->present();\n\n // app::render() -> Runtime::render() calls beginRenderFrameStats(); this\n // moves the counters into lastRenderFrameStats() so they can be read.\n core::render::publishRenderFrameStats();\n const core::render::RenderFrameStats& stats = core::render::lastRenderFrameStats();\n g_loop.rectDraws += stats.rectDraws;\n g_loop.textDraws += stats.textDraws;\n\n nextFrameTime += frameInterval;\n const double slack = nextFrameTime - core::window::timeSeconds();\n if (slack > 0.0) {\n std::this_thread::sleep_for(std::chrono::duration(slack));\n } else {\n // Fell behind: resynchronise instead of accumulating debt.\n nextFrameTime = core::window::timeSeconds();\n }\n }\n\n core::releaseInputQueue(window);\n backend->makeCurrent();\n backend->releaseRenderCache();\n {\n core::render::ScopedRenderBackend scopedBackend(*backend);\n app::shutdown();\n }\n backend.reset();\n core::window::destroyWindow(window);\n glfwTerminate();\n\n if (g_loop.frames < kFrameBudget) {\n std::println(\"loop stalled: {} of {} frames in {:.2f}s\",\n g_loop.frames, kFrameBudget, g_loop.elapsed);\n return 14;\n }\n if (g_loop.rectDraws <= 0) {\n std::println(\"no rect geometry reached the backend over {} frames\", g_loop.frames);\n return 15;\n }\n if (g_loop.textDraws <= 0) {\n // Text needs the freetype path AND a usable font. On a box with no\n // system font at any of upstream's Linux fallback paths this is the\n // failure you get, and it is worth distinguishing from a dead backend.\n std::println(\"no text geometry reached the backend over {} frames \"\n \"(freetype path or system font missing)\", g_loop.frames);\n return 16;\n }\n\n std::println(\"compat.eui-neo[own main]: ok ({} frames in {:.2f}s, {} rect / {} text draws)\",\n g_loop.frames, g_loop.elapsed, g_loop.rectDraws, g_loop.textDraws);\n return 0;\n}\n\n} // namespace\n\nint main() {\n // Compiling + linking runWindow() is the headless test: it is the only thing\n // in this index that instantiates core::window / core::render / app::* from a\n // consumer TU. The run itself needs a display (opt-in).\n if (std::getenv(\"MCPP_RUN_WINDOW\") != nullptr) {\n return runWindow();\n }\n std::println(\"compat.eui-neo[own main]: linked; window run is opt-in (MCPP_RUN_WINDOW=1)\");\n return 0;\n}\n" + } + ] + }, + { + "project": "eui-neo", + "text": "[package]\nname = \"eui-neo-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\neui-neo = \"0.6.0\"\n", + "files": [ + { + "path": "tests/examples/eui-neo/tests/header.cpp", + "text": "// Behavioral test — verify compat.eui-neo builds, exposes its umbrella header\n// to a Form A consumer, and links against real symbols from the built lib.\n//\n// `#include ` is the whole point of the header-compat shape. The\n// umbrella declares the DSL app surface (eui/dsl_app.h) and leaves two symbols\n// for the application to supply — app::dslAppConfig() and app::compose().\n// Since 0.5.6 the umbrella no longer pulls in eui/detail/dsl_app_impl.h (the\n// impl lives in the app-main entry points instead), but THIS TU only defines\n// the two app symbols and never calls app::initialize/update/render — so it\n// links without the impl header, and defining the app symbols is exactly what\n// upstream's examples/*.cpp do, making this test a faithful minimal consumer.\n//\n// The assertion itself runs on eui::json::Document: it lives in\n// core/platform/json.cpp, so a package that compiled zero translation units\n// fails at LINK time instead of silently passing — which is how an earlier\n// revision of this descriptor shipped an empty lib behind a green CI. It is\n// also the cheapest entry point that is genuinely headless (no window, no GL\n// context), which matters because CI runners have no display.\n#include \nimport std;\n\nnamespace app {\n\nconst DslAppConfig& dslAppConfig() {\n static const DslAppConfig config = DslAppConfig{}\n .title(\"compat.eui-neo smoke test\")\n .windowSize(320, 240);\n return config;\n}\n\n// Never invoked: the test asserts on the headless JSON facade instead of\n// entering the render loop. It exists so the DSL app skeleton links.\nvoid compose(eui::Ui&, const eui::Screen&) {}\n\n} // namespace app\n\nint main() {\n // The umbrella header really did reach us, with the DSL app config intact.\n if (app::dslAppConfig().windowWidthValue != 320) {\n std::println(\"dslAppConfig() not wired: width={}\", app::dslAppConfig().windowWidthValue);\n return 1;\n }\n\n eui::json::Document doc;\n if (!doc.parse(R\"({\"framework\": {\"name\": \"eui-neo\", \"version\": 3}})\")) {\n std::println(\"parse failed: {}\", doc.error().message);\n return 2;\n }\n\n std::string name;\n if (!doc.stringAt(\"/framework/name\", name) || name != \"eui-neo\") {\n std::println(\"unexpected /framework/name: '{}'\", name);\n return 3;\n }\n\n // number() rather than signedInteger(): yyjson tags a positive literal as\n // an UNSIGNED integer, so yyjson_is_sint() — and therefore\n // Value::signedInteger() — is false for `3`.\n double version = 0.0;\n if (!doc.atPointer(\"/framework/version\").number(version) || version != 3.0) {\n std::println(\"unexpected /framework/version: {}\", version);\n return 4;\n }\n\n // Forces core/platform/platform.cpp into the link. That TU emits\n // `platform.o`, which collides with compat.glfw's src/platform.c in mcpp's\n // flat per-link obj/ directory (mcpp#233/#240) and used to be dropped\n // silently — the descriptor now routes it through a uniquely named\n // generated stub. Referencing a symbol only that TU defines turns a\n // regression into an undefined reference instead of a passing test.\n // Read-only on purpose: requestFrame() would reach glfwPostEmptyEvent()\n // with no window, and CI runners are headless. Both flags start clear.\n if (core::platform::consumeFrameRequest() || core::platform::consumeUiUpdate()) {\n std::println(\"platform frame flags did not start clear\");\n return 5;\n }\n\n // NEGATIVE verification for the `markdown` feature, which is NOT requested\n // by this member. components/markdown.h ships two definitions of\n // parseMarkdownBlocks: the md4c one, and — when EUI_HAS_MD4C is absent — a\n // degenerate fallback that wraps the entire source in a single Paragraph.\n // Getting the fallback here proves the feature's interface define really is\n // gated off by default. tests/examples/eui-neo-markdown asserts the other\n // side of the same switch.\n const auto blocks = components::detail::parseMarkdownBlocks(\"# Heading\\n\\nBody text.\\n\");\n if (blocks.size() != 1 || blocks[0].kind != components::detail::MarkdownBlockKind::Paragraph) {\n std::println(\"markdown feature leaked into the default build: {} block(s)\", blocks.size());\n return 6;\n }\n\n std::println(\"compat.eui-neo smoke test: ok (parsed {} v{}, markdown gated off)\", name, version);\n return 0;\n}\n" + } + ] + }, + { + "project": "expat", + "text": "# compat.expat test project.\n#\n# Expat is built from source here while the ecosystem also carries\n# `xim:expat` 2.6.2 as a transitive of `xim:mesa`. They share the soname\n# `libexpat.so.1`, so exactly one is mapped and nothing warns — the dladdr\n# assertion pins which.\n[package]\nname = \"expat-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nexpat = \"2.7.1\"\n", + "files": [ + { + "path": "tests/examples/expat/tests/xml.cpp", + "text": "// compat.expat — behavioral test. It parses a document rather than checking\n// that symbols resolve, because the interesting failures are configuration\n// ones: XML_DTD, XML_GE and XML_NS are feature switches compiled into\n// expat_config.h, and getting them wrong yields a parser that links perfectly\n// and then rejects (or mis-splits) input.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n\nstruct Collected {\n std::vector elements;\n std::string text;\n};\n\nvoid XMLCALL on_start(void *ud, const XML_Char *name, const XML_Char **atts)\n{\n auto *c = static_cast(ud);\n c->elements.emplace_back(name);\n for (int i = 0; atts != nullptr && atts[i] != nullptr; i += 2) {\n c->elements.emplace_back(std::string(\"@\") + atts[i] + \"=\" + atts[i + 1]);\n }\n}\n\nvoid XMLCALL on_text(void *ud, const XML_Char *s, int len)\n{\n static_cast(ud)->text.append(s, static_cast(len));\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. A namespaced parse, which is XML_NS ───────────────────────────\n // The separator argument only does anything when XML_NS was compiled in;\n // without it the element name comes back as \"w:seat\" instead of the\n // expanded \"urn:test|seat\", and wayland-scanner would misread every\n // protocol file.\n {\n Collected c;\n XML_Parser p = XML_ParserCreateNS(nullptr, '|');\n check(p != nullptr, \"XML_ParserCreateNS returns a parser\");\n XML_SetUserData(p, &c);\n XML_SetElementHandler(p, on_start, nullptr);\n XML_SetCharacterDataHandler(p, on_text);\n\n static const char doc[] =\n \"hi\";\n const bool ok = XML_Parse(p, doc, (int)std::strlen(doc), 1) == XML_STATUS_OK;\n check(ok, \"XML_Parse accepts a namespaced document\");\n check(c.elements.size() >= 2 && c.elements[0] == \"urn:test|proto\",\n \"XML_NS expanded the prefix (urn:test|proto)\");\n check(c.text == \"hi\", \"character data reached the handler\");\n XML_ParserFree(p);\n }\n\n // ── 2. An internal DTD entity, which is XML_DTD + XML_GE ─────────────\n // Built without general-entity support this parse fails outright, so it\n // separates a correct expat_config.h from a plausible one.\n {\n Collected c;\n XML_Parser p = XML_ParserCreate(nullptr);\n XML_SetUserData(p, &c);\n XML_SetCharacterDataHandler(p, on_text);\n\n static const char doc[] =\n \"]>&who;\";\n const bool ok = XML_Parse(p, doc, (int)std::strlen(doc), 1) == XML_STATUS_OK;\n check(ok, \"XML_Parse accepts an internal DTD entity\");\n check(c.text == \"wayland\", \"the general entity expanded to 'wayland'\");\n XML_ParserFree(p);\n }\n\n // ── 3. Malformed input is rejected ───────────────────────────────────\n {\n XML_Parser p = XML_ParserCreate(nullptr);\n static const char bad[] = \"\";\n check(XML_Parse(p, bad, (int)std::strlen(bad), 1) == XML_STATUS_ERROR,\n \"mismatched tags are an error, not silence\");\n XML_ParserFree(p);\n }\n\n // ── 4. It is THIS build that ran ─────────────────────────────────────\n // The package is `kind = \"lib\"`, so these objects are merged into the\n // consumer and dladdr reports the EXECUTABLE. The check that matters is\n // the same either way: the code that just parsed must not have come from\n // the ecosystem's `xim:expat`, which is present whenever Mesa is and would\n // otherwise answer silently.\n //\n // A STATICALLY LINKED BINARY CANNOT ANSWER THIS, AND THAT IS NOT A\n // FAILURE. musl's `dladdr` is `weak_alias(stub_dladdr, dladdr)` returning\n // 0 (src/ldso/dladdr.c); the dynamic loader overrides it, and a static\n // link has no loader to do so. An openkal program is statically linked, so\n // the call returns 0 for every address, and the risk this check exists for\n // cannot arise there either: nothing was loaded at runtime, so the only\n // expat in the process is the one that was linked in.\n //\n // SKIPPING IT SILENTLY WOULD MAKE \"no loader\" AND \"the loader answered\n // wrongly\" THE SAME READING, so the fallback asserts what distinguishes\n // them. `main` is in this executable by construction; if `dladdr` cannot\n // locate that either, there is no dynamic symbol information at all and\n // the question is moot. If it locates `main` but not `XML_ParserCreate`,\n // something is wrong and the check fails as before.\n {\n Dl_info info{};\n const bool located =\n ::dladdr(reinterpret_cast(&XML_ParserCreate), &info) != 0\n && info.dli_fname != nullptr;\n if (located) {\n const std::string from = info.dli_fname;\n std::printf(\" resolved from: %s\\n\", from.c_str());\n check(from.find(\"xim-x-expat\") == std::string::npos,\n \"it is not the ecosystem payload's libexpat\");\n } else {\n Dl_info self{};\n const bool anyAnswer =\n ::dladdr(reinterpret_cast(&main), &self) != 0;\n std::printf(\" dladdr answers nothing in this image%s\\n\",\n anyAnswer ? \" EXCEPT main\" : \" at all (static link)\");\n check(!anyAnswer,\n \"dladdr locates the code that parsed, or locates nothing\");\n }\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "ffmpeg-module", + "text": "# Public `ffmpeg` C++23-module package test (bare-name consumption from the\n# builtin default namespace — resolved against THIS checkout via the\n# workspace-root `[indices]` redirect, which every member inherits). Replaces the\n# former tests/smoke_ffmpeg_module.sh reseeding driver. The package is\n# The package builds on linux + macOS + windows (compat.ffmpeg 3-platform).\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `default`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nffmpeg = { path = \"../../..\" }\n\n[package]\nname = \"ffmpeg-module-tests\"\nversion = \"0.1.0\"\n\n# The module package's own transitive dep (compat.ffmpeg) resolves from the\n# GLOBAL published compat index: this member redirects `default`, and a\n# member-level [indices] replaces the root's `compat` rather than adding to\n# it. The compat descriptor itself is pre-merge-validated by its own member.\n[target.'cfg(linux)'.dependencies.ffmpeg]\nffmpeg = \"8.1.2\"\n\n[target.'cfg(macos)'.dependencies.ffmpeg]\nffmpeg = \"8.1.2\"\n\n[target.'cfg(windows)'.dependencies.ffmpeg]\nffmpeg = \"8.1.2\"\n", + "files": [ + { + "path": "tests/examples/ffmpeg-module/tests/ffmpeg_module.cpp", + "text": "// Public ffmpeg module package end-to-end assertion: import-only consumption\n// (`import ffmpeg.av;`), libavutil version of the FFmpeg 8.1.x train, core\n// decoders present, demuxer registry populated. Builds on linux+macOS+windows.\nimport std;\nimport ffmpeg.av;\n\nint main() {\n unsigned v { avutil_version() };\n std::println(\"libavutil {}.{} module package ok\", v >> 16, (v >> 8) & 0xff);\n if ((v >> 16) != 60u) { // libavutil major of the FFmpeg 8.1.x train\n return 1;\n }\n\n for (auto name : { \"h264\", \"hevc\", \"av1\", \"aac\" }) {\n const AVCodec* dec { avcodec_find_decoder_by_name(name) };\n if (dec == nullptr) {\n std::println(\"decoder {} MISSING\", name);\n return 2;\n }\n }\n\n void* it { nullptr };\n long demuxers { 0 };\n while (av_demuxer_iterate(&it)) ++demuxers;\n std::println(\"{} demuxers registered\", demuxers);\n return demuxers > 300 ? 0 : 3;\n}\n" + } + ] + }, + { + "project": "ffmpeg", + "text": "# FFmpeg test project. compat.ffmpeg compiles all of FFmpeg 8.1.2 from source\n# with mcpp itself (2281 TUs incl. 157 NASM .asm; no configure/make at build\n# time — the config snapshot ships as generated_files in the descriptor).\n# Linux + macOS: under those cfgs the dep is pulled (per-OS frozen config\n# per-OS frozen config snapshot — linux NASM, macosx NEON, windows win64\n# NASM) and the test asserts the full component surface on all three.\n[package]\nname = \"ffmpeg-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nffmpeg = \"8.1.2\"\n\n[target.'cfg(macos)'.dependencies.compat]\nffmpeg = \"8.1.2\"\n\n[target.'cfg(windows)'.dependencies.compat]\nffmpeg = \"8.1.2\"\n", + "files": [ + { + "path": "tests/examples/ffmpeg/tests/ffmpeg.cpp", + "text": "// compat.ffmpeg end-to-end assertion: the full-profile source build must\n// expose its major decoders and demuxers, and library versions must match the\n// vendored 8.1.2 release. Linux-only (see mcpp.toml).\n#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)\nextern \"C\" {\n#include \n#include \n}\n\nint main() {\n if ((avutil_version() >> 16) != 60) return 1; // n8.1.x: libavutil major 60\n if ((avcodec_version() >> 16) != 62) return 2;\n if ((avformat_version() >> 16) != 62) return 3;\n\n const char* decoders[] { \"h264\", \"hevc\", \"av1\", \"aac\" };\n for (const char* name : decoders)\n if (!avcodec_find_decoder_by_name(name)) return 4;\n\n void* it { nullptr };\n int demuxers { 0 };\n while (av_demuxer_iterate(&it)) ++demuxers;\n if (demuxers <= 300) return 5; // full profile registers 358\n\n return 0;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "fmtlib.fmt", + "text": "# {fmt} test project: consumes the C++23 module `fmt` (`import fmt;`, no\n# #include) from the `fmtlib.fmt` package and asserts behavior under `mcpp test`.\n# Part of the mcpp-index self-referential workspace: the workspace-root\n# `[indices]` redirect points at this repo, so the dependency resolves to the\n# checked-in recipe (pkgs/f/\n# fmtlib.fmt.lua) rather than a remote index. The exported module is plain `fmt`.\n[indices]\nfmtlib = { path = \"../../..\" }\n\n[package]\nname = \"fmtlib-fmt-tests\"\nversion = \"0.1.0\"\n\n[dependencies.fmtlib]\nfmt = \"12.2.0\"\n", + "files": [ + { + "path": "tests/examples/fmtlib.fmt/tests/format_test.cpp", + "text": "// Behavioral test: consume {fmt}'s compiled implementation through the C++23\n// module `fmt` (`import fmt;`, no #include) and assert the results. Exercises\n// src/format.cc via the module unit's tail includes — not just header inlines.\n// Returns non-zero on any mismatch.\nimport std;\nimport fmt;\n\nint main() {\n std::string a = fmt::format(\"{} + {} = {}\", 2, 3, 2 + 3);\n std::string b = fmt::format(\"{:08.3f}\", 3.14159);\n std::string c = fmt::format(\"{0}-{1}-{0}\", \"x\", \"y\");\n std::string d = fmt::format(\"{:#x}\", 255);\n\n bool ok = a == \"2 + 3 = 5\"\n && b == \"0003.142\"\n && c == \"x-y-x\"\n && d == \"0xff\";\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "fontconfig", + "text": "# fontconfig test member — the three generated tables.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"fontconfig-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# `compat.expat` and `compat.freetype` arrive transitively; naming them here as\n# version deps as well is fine because the package declares them the same way\n# (not as path deps), unlike the wayland members.\n[target.'cfg(linux)'.dependencies.freedesktop]\nfontconfig = \"2.15.0\"\n", + "files": [ + { + "path": "tests/examples/fontconfig/tests/fontconfig.cpp", + "text": "// freedesktop.fontconfig — the three generated tables, exercised through the\n// public API.\n//\n// WHAT CAN BE ASSERTED WITHOUT FONTS\n//\n// This package compiles the four runtime paths EMPTY on purpose (see the\n// manifest), so `FcInit()` on a runner with no `FONTCONFIG_FILE` finds no\n// configuration and no fonts. That is the intended behaviour, not a defect,\n// and it means a font-matching test would only be asserting that the runner\n// has a desktop.\n//\n// The three GENERATED tables need none of that, and they are what this fork\n// exists to produce:\n//\n// fclang.h 281 .orth files compiled into charset bitmaps\n// fccase.h Unicode case folding\n// fcobjshash.h object-name → id, the gperf replacement\n//\n// Each is reachable through a public entry point that takes no config.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\" FcGetVersion() = %d\\n\", FcGetVersion());\n check(FcGetVersion() >= 21500, \"FcGetVersion reports 2.15.0 or newer\");\n\n // ── fcobjshash.h — the gperf replacement ─────────────────────────────\n //\n // Exercised through `FcNameParse`, which is the public route into the\n // object table: the parser maps every \"name=value\" key through\n // `FcObjectLookupIdByName`, which IS the generated lookup. `FcObject` and\n // `FcObjectFromName` are internal (fcint.h), so reaching for them would\n // test the table without testing that a consumer can get to it.\n {\n FcPattern *p = FcNameParse((const FcChar8 *) \"Arial:pixelsize=12:slant=100\");\n check(p != nullptr, \"fcobjshash: FcNameParse accepted three object names\");\n if (p != nullptr) {\n FcChar8 *fam = nullptr;\n double px = 0;\n int slant = 0;\n const bool gf = FcPatternGetString(p, FC_FAMILY, 0, &fam) == FcResultMatch;\n const bool gp = FcPatternGetDouble(p, FC_PIXEL_SIZE, 0, &px) == FcResultMatch;\n const bool gs = FcPatternGetInteger(p, FC_SLANT, 0, &slant) == FcResultMatch;\n std::printf(\" family=%s pixelsize=%g slant=%d\\n\",\n gf && fam ? (const char *) fam : \"(none)\", px, slant);\n check(gf && fam && std::strcmp((const char *) fam, \"Arial\") == 0,\n \"…and \\\"family\\\" round-tripped to the right object\");\n check(gp && px == 12.0, \"…and \\\"pixelsize\\\" did too\");\n check(gs && slant == 100, \"…and \\\"slant\\\" did too\");\n FcPatternDestroy(p);\n }\n\n // A name the table does not contain must not resolve to a builtin —\n // fontconfig assigns it a dynamic id instead. This is the branch a\n // binary search gets wrong when its bounds are off by one.\n FcPattern *q = FcNameParse((const FcChar8 *) \":nosuchproperty=1\");\n check(q != nullptr, \"…and an unknown property does not crash the parser\");\n if (q) FcPatternDestroy(q);\n }\n\n // ── fclang.h — the language charsets ─────────────────────────────────\n // `FcLangGetCharSet` indexes straight into fcLangCharSets. Three languages\n // with very different coverage, so a table that was truncated or misordered\n // fails on at least one.\n for (const char *l : {\"en\", \"ru\", \"zh-cn\"}) {\n const FcCharSet *cs = FcLangGetCharSet((const FcChar8 *) l);\n const FcChar32 n = cs ? FcCharSetCount(cs) : 0;\n std::printf(\" lang %-6s -> %u codepoints\\n\", l, n);\n check(cs != nullptr && n > 0, \"fclang: the language has a charset\");\n }\n // Latin and Cyrillic must not be the same set — they are separate entries\n // whose leaves were de-duplicated, and a dedup bug shows up here.\n {\n const FcCharSet *en = FcLangGetCharSet((const FcChar8 *) \"en\");\n const FcCharSet *ru = FcLangGetCharSet((const FcChar8 *) \"ru\");\n check(en && ru && !FcCharSetEqual(en, ru),\n \"…and English and Russian are different sets\");\n check(en && FcCharSetHasChar(en, 'A'), \"…and English covers 'A'\");\n check(ru && FcCharSetHasChar(ru, 0x0416), \"…and Russian covers U+0416\");\n check(en && !FcCharSetHasChar(en, 0x0416), \"…and English does not\");\n }\n\n // A language that does not exist must return null rather than index out of\n // range — the fastpath table `fcLangCharSetRanges` is what decides that.\n check(FcLangGetCharSet((const FcChar8 *) \"zz-nowhere\") == nullptr,\n \"fclang: an unknown language returns null\");\n\n // ── fccase.h — Unicode case folding ──────────────────────────────────\n // `FcStrDowncase` walks fcCaseFold. ASCII exercises the EVEN_ODD/RANGE\n // methods; U+0130 (Latin capital I with dot) is a FULL fold — one char to\n // several — which is the method with its own byte pool.\n {\n FcChar8 *d = FcStrDowncase((const FcChar8 *) \"ABC\");\n std::printf(\" downcase(\\\"ABC\\\") = %s\\n\", d ? (const char *) d : \"(null)\");\n check(d && std::strcmp((const char *) d, \"abc\") == 0,\n \"fccase: ASCII folds through the range methods\");\n if (d) FcStrFree(d);\n }\n {\n // U+0410 CYRILLIC CAPITAL A -> U+0430, a two-byte fold.\n FcChar8 *d = FcStrDowncase((const FcChar8 *) \"\\xd0\\x90\");\n const bool ok = d && std::strcmp((const char *) d, \"\\xd0\\xb0\") == 0;\n std::printf(\" downcase(U+0410) = %s\\n\", ok ? \"U+0430\" : \"(wrong)\");\n check(ok, \"…and a Cyrillic capital folds to its lowercase\");\n if (d) FcStrFree(d);\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "freetype", + "text": "[package]\nname = \"freetype-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nfreetype = \"2.13.3\"\n", + "files": [ + { + "path": "tests/examples/freetype/tests/init.cpp", + "text": "// Smoke test — verify compat.freetype builds, links, and basic API works\n#include \n#include FT_FREETYPE_H\nimport std;\n\nint main() {\n FT_Library library{};\n FT_Error error = FT_Init_FreeType(&library);\n if (error) {\n std::println(\"FT_Init_FreeType failed: {}\", error);\n return 1;\n }\n std::println(\"compat.freetype smoke test: ok\");\n FT_Done_FreeType(library);\n return 0;\n}\n" + } + ] + }, + { + "project": "fribidi", + "text": "# compat.fribidi — the bidi algorithm pango needs for right-to-left text.\n[indices]\ncompat = { path = \"../../..\" }\n\n[package]\nname = \"fribidi-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\nfribidi = \"1.0.16\"\n", + "files": [ + { + "path": "tests/examples/fribidi/tests/fribidi.cpp", + "text": "// compat.fribidi — the Unicode bidirectional algorithm, on text that needs it.\n//\n// WHAT THIS ASSERTS AND WHY\n//\n// The tables are the whole package. fribidi's seven `.tab.i` files encode the\n// Unicode Character Database, and a build that compiled none of them would\n// still link, still run, and quietly treat Arabic as left-to-right. So every\n// check below asks the library a question whose answer is IN a table:\n//\n// the Unicode version fribidi-unicode-version.h, generated upstream\n// bidi character types bidi-type.tab.i\n// base direction the resolution algorithm over those types\n// visual reordering UAX #9 itself, the reason pango needs this\n// mirroring mirroring.tab.i — \"(\" becomes \")\" in an RTL run\n// joining types joining-type.tab.i, which shapes Arabic\n//\n// A pass here means the tarball's pre-generated tables really were compiled —\n// the claim that lets this be a descriptor instead of a fork.\n\n#ifdef __linux__\n\n// ⚠️ WRAPPED IN extern \"C\". fribidi's public headers have no `extern \"C\"` of\n// their own — measured, zero occurrences across fribidi.h and the fifteen\n// headers it pulls — so a C++ translation unit mangles every declaration and\n// the link fails naming symbols the library plainly contains. Same situation\n// as compat.libseat in this index, and the same answer: the consumer wraps.\nextern \"C\" {\n#include \n}\n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. the version, and the Unicode version behind the tables ────────\n std::printf(\" %s\\n\", fribidi_version_info);\n check(std::strstr(fribidi_version_info, \"1.0.16\") != nullptr,\n \"fribidi_version_info names 1.0.16, as the descriptor does\");\n std::printf(\" Unicode %s\\n\", FRIBIDI_UNICODE_VERSION);\n check(FRIBIDI_UNICODE_VERSION[0] >= '1',\n \"FRIBIDI_UNICODE_VERSION came from the generated header\");\n\n // ── 2. bidi character types — bidi-type.tab.i ────────────────────────\n // 'a' is L (left-to-right). U+0627 ARABIC LETTER ALEF is AL (arabic\n // letter). U+05D0 HEBREW LETTER ALEF is R. Three different answers from\n // one table; a build with no table would give the same answer for all.\n const FriBidiCharType t_a = fribidi_get_bidi_type(0x0061);\n const FriBidiCharType t_alef = fribidi_get_bidi_type(0x0627);\n const FriBidiCharType t_hebr = fribidi_get_bidi_type(0x05D0);\n std::printf(\" types: 'a'=%s U+0627=%s U+05D0=%s\\n\",\n fribidi_get_bidi_type_name(t_a),\n fribidi_get_bidi_type_name(t_alef),\n fribidi_get_bidi_type_name(t_hebr));\n // There is no FRIBIDI_IS_LTR: the header offers FRIBIDI_IS_RTL and the\n // left-to-right case is its negation, which is the honest spelling anyway\n // because \"not RTL\" is what the algorithm actually decides.\n check(FRIBIDI_IS_LETTER(t_a) && !FRIBIDI_IS_RTL(t_a), \"'a' is a left-to-right letter\");\n check(FRIBIDI_IS_ARABIC(t_alef), \"U+0627 ARABIC ALEF is an Arabic letter\");\n check(FRIBIDI_IS_RTL(t_hebr) && !FRIBIDI_IS_ARABIC(t_hebr),\n \"U+05D0 HEBREW ALEF is right-to-left but not Arabic\");\n\n // ── 3. base direction of a paragraph ─────────────────────────────────\n // The algorithm scans for the first strong character. A Hebrew-initial\n // paragraph is RTL; a Latin-initial one is LTR. This is what pango calls\n // to decide which way a line runs.\n // ⚠️ fribidi_get_par_direction takes BIDI TYPES, not characters. Passing\n // code points compiles — FriBidiCharType and FriBidiChar are both 32-bit\n // unsigned — and returns a plausible wrong answer, which is why the check\n // below reads the types out of the table first.\n {\n const FriBidiChar rtl[] = {0x05D0, 0x0020, 0x0041}; // ALEF SPACE A\n FriBidiCharType types[3];\n fribidi_get_bidi_types(rtl, 3, types);\n check(FRIBIDI_IS_RTL(fribidi_get_par_direction(types, 3)),\n \"a Hebrew-initial paragraph resolves to RTL\");\n\n const FriBidiChar ltr[] = {0x0041, 0x0020, 0x05D0}; // A SPACE ALEF\n fribidi_get_bidi_types(ltr, 3, types);\n check(!FRIBIDI_IS_RTL(fribidi_get_par_direction(types, 3)),\n \"…and a Latin-initial one to LTR\");\n }\n\n // ── 4. reordering: the point of the library ──────────────────────────\n // \"abc\" + Hebrew \"אבג\" in logical order, in an RTL paragraph. UAX #9 puts\n // the Hebrew run first visually and REVERSES it, because Hebrew is stored\n // in logical order and drawn right to left.\n //\n // This is an INTERMEDIATE QUANTITY, not a rendered image: it says exactly\n // where the algorithm put each character, so a wrong answer points at the\n // reordering rather than at \"the text looked odd\".\n {\n FriBidiChar logical[] = {0x0061, 0x0062, 0x0063, 0x0020,\n 0x05D0, 0x05D1, 0x05D2}; // \"abc אבג\"\n const FriBidiStrIndex len = 7;\n FriBidiCharType types[7];\n FriBidiBracketType brackets[7];\n FriBidiLevel levels[7];\n FriBidiChar visual[7];\n FriBidiParType par = FRIBIDI_PAR_RTL;\n\n fribidi_get_bidi_types(logical, len, types);\n fribidi_get_bracket_types(logical, len, types, brackets);\n const FriBidiLevel max = fribidi_get_par_embedding_levels_ex(\n types, brackets, len, &par, levels);\n check(max > 0, \"fribidi_get_par_embedding_levels_ex resolved levels\");\n\n std::memcpy(visual, logical, sizeof logical);\n FriBidiStrIndex map[7];\n for (FriBidiStrIndex i = 0; i < len; ++i) {\n map[i] = i;\n }\n // warn_unused_result: the return is the maximum level reached, and\n // ignoring it is a warning that CI treats as noise worth removing.\n const FriBidiLevel rmax = fribidi_reorder_line(\n FRIBIDI_FLAGS_DEFAULT, types, len, 0, par, levels, visual, map);\n check(rmax > 0, \"fribidi_reorder_line reordered at least one level\");\n\n std::printf(\" visual order: \");\n for (FriBidiStrIndex i = 0; i < len; ++i) {\n std::printf(\"U+%04X \", visual[i]);\n }\n std::printf(\"\\n\");\n\n // In an RTL paragraph the Hebrew run comes first and is reversed:\n // U+05D2 U+05D1 U+05D0, then the space, then \"abc\" left to right.\n check(visual[0] == 0x05D2 && visual[1] == 0x05D1 && visual[2] == 0x05D0,\n \"the Hebrew run is placed first and reversed\");\n check(visual[4] == 0x0061 && visual[5] == 0x0062 && visual[6] == 0x0063,\n \"…and the Latin run keeps its own order\");\n }\n\n // ── 5. mirroring — mirroring.tab.i ───────────────────────────────────\n // In a right-to-left run, \"(\" must be DRAWN as \")\". The glyph substitution\n // is a table lookup, and a build without the table returns the input.\n {\n FriBidiChar mirrored = 0;\n const bool got = fribidi_get_mirror_char(0x0028, &mirrored) != 0;\n std::printf(\" mirror of '(' = U+%04X\\n\", mirrored);\n check(got && mirrored == 0x0029, \"'(' mirrors to ')'\");\n\n FriBidiChar none = 0;\n check(fribidi_get_mirror_char(0x0061, &none) == 0,\n \"…and 'a' has no mirror, rather than mirroring to itself\");\n }\n\n // ── 6. joining types — joining-type.tab.i ────────────────────────────\n // Arabic letters change shape by position. ALEF is right-joining (it links\n // to the preceding letter only); BEH is dual-joining. Getting this wrong\n // is what makes Arabic render as disconnected letterforms.\n {\n const FriBidiJoiningType j_alef = fribidi_get_joining_type(0x0627);\n const FriBidiJoiningType j_beh = fribidi_get_joining_type(0x0628);\n std::printf(\" joining: ALEF=%s BEH=%s\\n\",\n fribidi_get_joining_type_name(j_alef),\n fribidi_get_joining_type_name(j_beh));\n check(FRIBIDI_IS_JOINING_TYPE_R(j_alef), \"ARABIC ALEF is right-joining\");\n check(FRIBIDI_IS_JOINING_TYPE_D(j_beh), \"ARABIC BEH is dual-joining\");\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "ftxui-module", + "text": "# compat.ftxui's `modules` feature: upstream's own named modules for 7.0.3 —\n# the `ftxui` umbrella plus ftxui.component/.dom/.screen/.util. The sibling\n# member tests/examples/core covers the default HEADER surface at the same\n# version and requests no feature; this member exists to prove the feature.\n#\n# Part of the self-referential workspace: the dependency resolves to the\n# checked-in descriptor (pkgs/c/compat.ftxui.lua) through the workspace-root\n# `[indices]` redirect, which this member inherits.\n#\n# ⚠️ EVERY TU HERE STAYS ON THE MODULE SURFACE — `import std;` beside\n# `import ftxui...;`, and not one textual `#include`. That is not style, it is\n# the thing the member is guarding. A consumer TU that mixes `import ftxui;`\n# with a textual `#include` of a standard header hands gcc two copies of\n# libstdc++'s declarations through the sub-modules' global module fragments,\n# and gcc 16 rejects it with thousands of `redefinition of 'void\n# std::__terminate()'` / `conflicting declaration of template ... std::allocator`\n# errors. That is what took down #292's first attempt at compiling these units,\n# and it is a CONSUMER-side constraint, not a defect in the package. clang\n# accepts the mixed TU, which is why only the linux gcc leg went red there.\n#\n# Kept unmixed, the feature builds and runs on gcc 16.1.0 and llvm 22.1.8\n# alike, so this member needs no toolchain pin and runs on both linux legs.\n[package]\nname = \"ftxui-module-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nftxui = { version = \"7.0.3\", features = [\"modules\"] }\n", + "files": [ + { + "path": "tests/examples/ftxui-module/tests/sub_component.cpp", + "text": "// ftxui.component alone: Component/Event/Button and event routing.\nimport std;\nimport ftxui.component;\n\nint main() {\n using namespace ftxui;\n int clicked = 0;\n Component button = Button(\"go\", [&] { ++clicked; });\n\n Component container = Container::Vertical({button});\n if (!container->OnEvent(Event::Return)) return 1;\n if (clicked != 1) return 2;\n\n const Event a = Event::Character('a');\n if (!a.is_character() || a.character() != \"a\") return 3;\n if (Event::Return == a) return 4;\n\n std::println(\"clicked={}\", clicked);\n std::println(\"ftxui.component OK\");\n return 0;\n}\n" + }, + { + "path": "tests/examples/ftxui-module/tests/sub_dom.cpp", + "text": "// ftxui.dom alone: elements + Render + Screen, no umbrella, no headers.\nimport std;\nimport ftxui.dom;\n\nint main() {\n using namespace ftxui;\n Element document = vbox({text(\"dom-only\"), separator(), text(\"row2\")});\n auto screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));\n Render(screen, document);\n const std::string rendered = screen.ToString();\n std::println(\"dom rendered: [{}]\", rendered);\n if (rendered.find(\"dom-only\") == std::string::npos) return 1;\n if (rendered.find(\"row2\") == std::string::npos) return 2;\n std::println(\"ftxui.dom OK\");\n return 0;\n}\n" + }, + { + "path": "tests/examples/ftxui-module/tests/sub_screen.cpp", + "text": "// ftxui.screen alone: Screen/Pixel/Color/Terminal and the _rgb literal.\nimport std;\nimport ftxui.screen;\n\nint main() {\n using namespace ftxui;\n auto screen = Screen::Create(Dimensions{4, 2});\n screen.PixelAt(0, 0).character = \"X\";\n screen.PixelAt(3, 1).character = \"Y\";\n const std::string s = screen.ToString();\n std::println(\"screen: [{}]\", s);\n if (s.find('X') == std::string::npos) return 1;\n if (s.find('Y') == std::string::npos) return 2;\n\n const Color red = Color::Red;\n const Color rgb = Color::RGB(1, 2, 3);\n if (red == rgb) return 3;\n using namespace ftxui::literals;\n const Color lit = 0x0102ff_rgb;\n if (lit == red) return 4;\n\n if (string_width(\"abc\") != 3) return 5;\n if (to_string(to_wstring(std::string(\"mcpp\"))) != \"mcpp\") return 6;\n std::println(\"ftxui.screen OK\");\n return 0;\n}\n" + }, + { + "path": "tests/examples/ftxui-module/tests/sub_util.cpp", + "text": "// ftxui.util alone: Ref/ConstRef/StringRef and AutoReset.\nimport std;\nimport ftxui.util;\n\nint main() {\n using namespace ftxui;\n int backing = 7;\n Ref r(&backing);\n *r = 9;\n if (backing != 9) return 1;\n\n ConstRef cr(5);\n if (*cr != 5) return 2;\n\n std::string text = \"abc\";\n StringRef sr(&text);\n *sr = \"xyz\";\n if (text != \"xyz\") return 3;\n\n int guarded = 1;\n { AutoReset reset(&guarded, 42); if (guarded != 42) return 4; }\n if (guarded != 1) return 5;\n\n std::println(\"ftxui.util OK\");\n return 0;\n}\n" + }, + { + "path": "tests/examples/ftxui-module/tests/umbrella.cpp", + "text": "// `import ftxui;` — the umbrella module — plus a real dom render.\nimport std;\nimport ftxui;\n\nint main() {\n using namespace ftxui;\n Element document = hbox({text(\"compat\"), separator(), text(\"ftxui\")});\n auto screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));\n Render(screen, document);\n const std::string rendered = screen.ToString();\n std::println(\"rendered: [{}]\", rendered);\n if (rendered.find(\"compat\") == std::string::npos) return 1;\n if (rendered.find(\"ftxui\") == std::string::npos) return 2;\n // the separator must actually have drawn something between them\n if (rendered.find(\"compat\") > rendered.find(\"ftxui\")) return 3;\n std::println(\"umbrella OK\");\n return 0;\n}\n" + } + ] + }, + { + "project": "galay", + "text": "# Public Galay Form-A package consumer. The package is Unix-only in v5.0.2;\n# Windows still builds the no-op test below without resolving the dependency.\n[package]\nname = \"galay-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(unix)'.dependencies.gzj-creator]\ngalay = \"5.0.2\"\n\n[target.'cfg(unix)'.build]\ncxxflags = [\"-DHAVE_GALAY=1\"]\n\n# A member-level redirect replaces the workspace root's compat redirect, so\n# this test exercises the public package descriptor from this checkout.\n[indices]\ngzj-creator = { path = \"../../..\" }\n", + "files": [ + { + "path": "tests/examples/galay/tests/modules.cpp", + "text": "// Default Galay package surface: both named modules and linked kernel code.\n#ifdef HAVE_GALAY\n\nimport std;\nimport galay.utils;\nimport galay.kernel;\n\nint main() {\n using galay::kernel::Buffer;\n using galay::kernel::Host;\n using galay::kernel::IPType;\n using galay::utils::Base64Util;\n using galay::utils::StringUtils;\n\n const std::string encoded = Base64Util::Base64Encode(\"galay\");\n if (encoded != \"Z2FsYXk=\") return 1;\n if (Base64Util::Base64Decode(encoded) != \"galay\") return 2;\n\n const auto pieces = StringUtils::split(\"galay:mcpp:index\", ':');\n if (pieces != std::vector{\"galay\", \"mcpp\", \"index\"}) return 3;\n\n Buffer buffer(\"galay\", 5);\n if (buffer.length() != 5 || buffer.toString() != \"galay\") return 4;\n Buffer copy = buffer.clone();\n buffer.clear();\n if (copy.toString() != \"galay\" || buffer.length() != 0) return 5;\n\n const Host host(IPType::IPV4, \"127.0.0.1\", 9080);\n if (!host.valid() || host.ip() != \"127.0.0.1\" || host.port() != 9080) return 6;\n\n return 0;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "gio", + "text": "# gnome.gio — files, streams, D-Bus, and the GListModel pango needs.\n#\n# ⚠️ NAMES gio ALONE. glib, gobject and gmodule arrive transitively, and naming\n# any of them here would be `requested as both a version dep and a path dep`.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"gio-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\ngio = \"2.82.5\"\n", + "files": [ + { + "path": "tests/examples/gio/tests/gio.cpp", + "text": "// gnome.gio — exercised rather than merely linked.\n//\n// WHAT THIS ASSERTS AND WHY\n//\n// gio is 815 objects assembled from four different kinds of input, and each\n// kind fails a different way. A test that only said `#include ` and\n// linked would pass with any three of the four missing:\n//\n// upstream C gio/*.c — a missing one is an undefined reference,\n// which the linker DOES catch. Cheapest case.\n// VENDORED subtrees xdgmime/, inotify/, subprojects/gvdb/. Absent, gio\n// still links: g_content_type_guess returns\n// \"application/octet-stream\", the file monitor falls\n// back to polling, and GResource is never touched by\n// anything but glib-compile-resources. All three\n// degrade SILENTLY, so each is checked by name below.\n// CHECKED-IN CODEGEN mcpp/generated/*.c — 15,392 lines of gdbus-codegen\n// output that no build step here produces. Checked in\n// the FORK's own test, which can name the internal\n// symbol; regenerated and diffed by the fork's CI.\n// GENERATED HERE gioenumtypes.{h,c}, 82 GTypes from a reimplemented\n// glib-mkenums. ⭐ This is the one that already shipped\n// a bug once, in gnome.gobject 2.82.5: the macro names\n// were wrong (`G_UNICODE_TYPE_TYPE` for `G_TYPE_UNICODE_\n// TYPE`) and it compiled, linked and passed a test that\n// checked the FUNCTION and the NICK but never the MACRO.\n// So this file checks the macro, the nick, AND whether\n// the type is an enum or a flags — three independent\n// outputs of the same generator, which is what it takes.\n//\n// Nothing here needs a session bus, a network, or a mime database.\n\n#ifdef __linux__\n\n// ⚠️ NO extern \"C\" WRAPPER, and adding one BREAKS THIS UNDER libc++.\n//\n// gio decorates every header with G_BEGIN_DECLS/G_END_DECLS, which IS\n// `extern \"C\" {`, so a wrapper is redundant. It is also harmful: gio.h reaches\n// and , and libc++ routes those through\n// /, which define TEMPLATES. Inside an extern \"C\" block that\n// is `templates must have C++ linkage`, dozens of times, against a standard\n// header this test never names. libstdc++ does not route them that way, so the\n// gcc leg is green and the llvm leg is a wall.\n//\n// The rule: wrap a C header ONLY if it has no extern \"C\" of its own.\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-62s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// ── GFile: paths, URIs, and the round trip between them ─────────────────────\nvoid test_gfile()\n{\n GFile *f = g_file_new_for_path(\"/etc/hostname\");\n char *base = g_file_get_basename(f);\n char *uri = g_file_get_uri(f);\n check(base && std::strcmp(base, \"hostname\") == 0, \"GFile: basename of /etc/hostname\");\n check(uri && std::strcmp(uri, \"file:///etc/hostname\") == 0, \"GFile: to URI\");\n\n GFile *back = g_file_new_for_uri(uri);\n check(g_file_equal(f, back), \"GFile: URI round trip compares equal\");\n\n GFile *parent = g_file_get_parent(f);\n char *pp = parent ? g_file_get_path(parent) : nullptr;\n check(pp && std::strcmp(pp, \"/etc\") == 0, \"GFile: parent\");\n\n g_free(pp);\n g_free(base);\n g_free(uri);\n g_clear_object(&parent);\n g_clear_object(&back);\n g_object_unref(f);\n}\n\n// ── GListModel: the interface pango's PangoFontMap implements ───────────────\n//\n// ⭐ THIS IS WHY gio IS HERE. pango was blocked on exactly this symbol, so the\n// check is not \"gio works\" but \"the thing pango needs is a live GType\".\nvoid test_glistmodel()\n{\n check(G_TYPE_IS_INTERFACE(G_TYPE_LIST_MODEL), \"GListModel is a registered interface\");\n\n GListStore *store = g_list_store_new(G_TYPE_FILE);\n GFile *a = g_file_new_for_path(\"/a\");\n GFile *b = g_file_new_for_path(\"/b\");\n g_list_store_append(store, a);\n g_list_store_append(store, b);\n\n GListModel *m = G_LIST_MODEL(store);\n check(g_list_model_get_n_items(m) == 2, \"GListModel: n_items after two appends\");\n check(g_list_model_get_item_type(m) == G_TYPE_FILE, \"GListModel: item type\");\n\n GFile *got = static_cast(g_list_model_get_item(m, 1));\n char *p = got ? g_file_get_path(got) : nullptr;\n check(p && std::strcmp(p, \"/b\") == 0, \"GListModel: get_item(1) is the second file\");\n\n g_free(p);\n g_clear_object(&got);\n g_object_unref(a);\n g_object_unref(b);\n g_object_unref(store);\n}\n\n// ── gioenumtypes: 82 GTypes from the reimplemented glib-mkenums ─────────────\n//\n// Three DIFFERENT outputs of the generator, because they fail independently:\n//\n// the MACRO NAME mkenums derives it from the TYPE name, not the\n// enumerator prefix. Getting that backwards is what\n// shipped in gobject 2.82.5. A wrong macro is a compile\n// error HERE, which is the whole point of naming it.\n// the NICK public API — g_flags_get_value_by_nick reads it — and\n// gio overrides it seventeen times. Derived,\n// G_CONVERTER_NO_FLAGS would be \"no-flags\"; upstream says\n// \"none\".\n// ENUM vs FLAGS decided ONLY by a `/*< flags >*/` comment. gio has\n// exactly seven. GMountMountFlags carries it;\n// GConverterFlags does NOT, despite the name — so\n// upstream registers it as an ENUM, and a scanner that\n// guessed from the name would disagree with upstream's ABI.\nvoid test_enumtypes()\n{\n check(G_TYPE_IS_ENUM(G_TYPE_FILE_TYPE), \"gioenumtypes: G_TYPE_FILE_TYPE is an enum\");\n check(G_TYPE_IS_ENUM(G_TYPE_SOCKET_FAMILY), \"gioenumtypes: G_TYPE_SOCKET_FAMILY is an enum\");\n check(G_TYPE_IS_FLAGS(G_TYPE_MOUNT_MOUNT_FLAGS),\n \"gioenumtypes: GMountMountFlags has /*< flags >*/ -> FLAGS\");\n check(G_TYPE_IS_ENUM(G_TYPE_CONVERTER_FLAGS),\n \"gioenumtypes: GConverterFlags has NO annotation -> ENUM\");\n\n GEnumClass *fc = G_ENUM_CLASS(g_type_class_ref(G_TYPE_FILE_TYPE));\n GEnumValue *v = g_enum_get_value(fc, G_FILE_TYPE_DIRECTORY);\n check(v && std::strcmp(v->value_nick, \"directory\") == 0, \"gioenumtypes: derived nick\");\n check(v && std::strcmp(v->value_name, \"G_FILE_TYPE_DIRECTORY\") == 0,\n \"gioenumtypes: value name\");\n g_type_class_unref(fc);\n\n GEnumClass *cc = G_ENUM_CLASS(g_type_class_ref(G_TYPE_CONVERTER_FLAGS));\n GEnumValue *nf = g_enum_get_value(cc, G_CONVERTER_NO_FLAGS);\n check(nf && std::strcmp(nf->value_nick, \"none\") == 0,\n \"gioenumtypes: /*< nick=none >*/ overrides the derived \\\"no-flags\\\"\");\n g_type_class_unref(cc);\n\n GFlagsClass *mc = G_FLAGS_CLASS(g_type_class_ref(G_TYPE_MOUNT_MOUNT_FLAGS));\n check(g_flags_get_first_value(mc, G_MOUNT_MOUNT_NONE) != nullptr\n || G_MOUNT_MOUNT_NONE == 0,\n \"gioenumtypes: flags class is usable\");\n g_type_class_unref(mc);\n}\n\n// ── GConverter over zlib: compat.zlib is a real dependency, not a note ──────\nvoid test_zlib_converter()\n{\n const std::string plain(4096, 'x');\n\n GConverter *cz = G_CONVERTER(g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB, -1));\n GInputStream *src = g_memory_input_stream_new_from_data(plain.data(),\n static_cast(plain.size()),\n nullptr);\n GInputStream *comp = g_converter_input_stream_new(src, cz);\n\n // Both sides take GZlibCompressorFormat — there is no decompressor enum.\n GConverter *dz = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB));\n GInputStream *back = g_converter_input_stream_new(comp, dz);\n\n char buf[8192];\n gsize got = 0;\n GError *err = nullptr;\n gboolean ok = g_input_stream_read_all(back, buf, sizeof buf, &got, nullptr, &err);\n\n check(ok && got == plain.size() && std::memcmp(buf, plain.data(), got) == 0,\n \"zlib: compress -> decompress reproduces 4096 bytes\");\n if (err) {\n g_error_free(err);\n }\n g_object_unref(back);\n g_object_unref(dz);\n g_object_unref(comp);\n g_object_unref(src);\n g_object_unref(cz);\n}\n\n// ── GDBus introspection, with no bus ────────────────────────────────────────\nvoid test_dbus_introspection()\n{\n static const char xml[] =\n \"\"\n \" \"\n \" \"\n \" \"\n \" \"\n \" \"\n \" \"\n \" \"\n \"\";\n\n GError *err = nullptr;\n GDBusNodeInfo *node = g_dbus_node_info_new_for_xml(xml, &err);\n check(node != nullptr, \"GDBus: introspection XML parses\");\n\n GDBusInterfaceInfo *iface = node ? g_dbus_node_info_lookup_interface(node, \"org.example.Echo\")\n : nullptr;\n GDBusMethodInfo *m = iface ? g_dbus_interface_info_lookup_method(iface, \"Speak\") : nullptr;\n check(m && m->in_args && m->in_args[0] && std::strcmp(m->in_args[0]->signature, \"s\") == 0,\n \"GDBus: the parsed method's argument signature\");\n\n if (node) {\n g_dbus_node_info_unref(node);\n }\n if (err) {\n g_error_free(err);\n }\n}\n\n// ── gvdb: the reader upstream vendors under subprojects/ ────────────────────\n//\n// Without gvdb-reader.c gio does not fail to link — nothing in the library\n// calls it except GResource and GSettings' keyfile path, and both are lazy. So\n// the check is to hand GResource a blob that is definitely NOT a gvdb file and\n// require a CLEAN REFUSAL: a NULL return with GError set. That answer can only\n// come from gvdb_table_new_from_bytes, which is the file in question.\nvoid test_gvdb_reader()\n{\n static const char junk[] = \"this is not a gvdb table, not even slightly\";\n GBytes *b = g_bytes_new_static(junk, sizeof junk - 1);\n GError *err = nullptr;\n GResource *r = g_resource_new_from_data(b, &err);\n\n check(r == nullptr && err != nullptr,\n \"gvdb: a non-gvdb blob is refused with a GError, not a crash\");\n\n if (r) {\n g_resource_unref(r);\n }\n if (err) {\n g_error_free(err);\n }\n g_bytes_unref(b);\n}\n\n// ── xdgmime: the freedesktop mime reader behind g_content_type_* ────────────\n//\n// ⚠️ WHAT IS NOT ASSERTED: the answer for a given filename. That depends on\n// /usr/share/mime, which a CI container may not have — and a test that demanded\n// \"text/plain\" would be checking the RUNNER's mime database, not this build.\n// What IS asserted is the part xdgmime always answers: that the call returns a\n// content type at all, and that the two conversion helpers are mutual inverses.\nvoid test_content_type()\n{\n char *t = g_content_type_guess(\"notes.txt\", nullptr, 0, nullptr);\n check(t != nullptr && *t != '\\0', \"xdgmime: g_content_type_guess answers\");\n\n char *mime = g_content_type_get_mime_type(\"text/plain\");\n check(mime && std::strcmp(mime, \"text/plain\") == 0, \"xdgmime: mime type round trip\");\n\n check(g_content_type_is_a(\"text/plain\", \"text/plain\") == TRUE,\n \"xdgmime: a type is a subtype of itself\");\n\n g_free(mime);\n g_free(t);\n}\n\n// ── inotify: the Linux file-monitor backend ─────────────────────────────────\n//\n// Absent, `g_file_monitor_directory` still succeeds — GLocalFileMonitor falls\n// back to a polling implementation and nothing reports the difference. So the\n// check reads the TYPE NAME of what came back, which is the only place the two\n// are distinguishable.\nvoid test_inotify_backend()\n{\n GError *err = nullptr;\n GFile *dir = g_file_new_for_path(\"/tmp\");\n GFileMonitor *mon = g_file_monitor_directory(dir, G_FILE_MONITOR_NONE, nullptr, &err);\n\n check(mon != nullptr, \"GFileMonitor: /tmp can be watched\");\n if (mon) {\n const char *n = G_OBJECT_TYPE_NAME(mon);\n std::printf(\" (backend: %s)\\n\", n);\n check(n && std::strstr(n, \"Inotify\") != nullptr,\n \"inotify/: the backend is GInotifyFileMonitor, not the polling fallback\");\n g_file_monitor_cancel(mon);\n g_object_unref(mon);\n }\n if (err) {\n g_error_free(err);\n }\n g_object_unref(dir);\n}\n\n// ── networking types, without touching the network ─────────────────────────\nvoid test_networking()\n{\n GInetAddress *a = g_inet_address_new_from_string(\"127.0.0.1\");\n char *s = a ? g_inet_address_to_string(a) : nullptr;\n check(s && std::strcmp(s, \"127.0.0.1\") == 0, \"GInetAddress: parse and print\");\n check(a && g_inet_address_get_is_loopback(a), \"GInetAddress: 127.0.0.1 is loopback\");\n\n GSocketAddress *sa = a ? g_inet_socket_address_new(a, 8080) : nullptr;\n check(sa && g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(sa)) == 8080,\n \"GInetSocketAddress: port\");\n\n check(g_application_id_is_valid(\"org.example.App\"), \"GApplication: a valid id\");\n check(!g_application_id_is_valid(\"nope\"), \"GApplication: a bare word is not an id\");\n\n g_clear_object(&sa);\n g_free(s);\n g_clear_object(&a);\n}\n\n// ── the async machinery: GTask through a GMainContext ──────────────────────\nstruct AsyncState {\n GMainLoop *loop = nullptr;\n gssize got = -1;\n char buf[64] = {};\n};\n\nvoid on_read(GObject *src, GAsyncResult *res, gpointer user)\n{\n AsyncState *st = static_cast(user);\n st->got = g_input_stream_read_finish(G_INPUT_STREAM(src), res, nullptr);\n g_main_loop_quit(st->loop);\n}\n\nvoid test_async()\n{\n static const char payload[] = \"async\";\n AsyncState st;\n st.loop = g_main_loop_new(nullptr, FALSE);\n\n GInputStream *in = g_memory_input_stream_new_from_data(payload, sizeof payload - 1, nullptr);\n g_input_stream_read_async(in, st.buf, sizeof st.buf, G_PRIORITY_DEFAULT, nullptr,\n on_read, &st);\n g_main_loop_run(st.loop);\n\n check(st.got == 5 && std::memcmp(st.buf, payload, 5) == 0,\n \"GTask: read_async completes through the main loop\");\n\n g_object_unref(in);\n g_main_loop_unref(st.loop);\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\"gio %d.%d.%d\\n\\n\", glib_major_version, glib_minor_version, glib_micro_version);\n\n test_gfile();\n test_glistmodel();\n test_enumtypes();\n test_zlib_converter();\n test_dbus_introspection();\n test_gvdb_reader();\n test_content_type();\n test_inotify_backend();\n test_networking();\n test_async();\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\n#include \n\nint main()\n{\n std::printf(\"gio: this package builds the Unix half of gio; skipping.\\n\");\n return 0;\n}\n\n#endif\n" + }, + { + "path": "tests/examples/gio/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. The test\n// next to this file consumes gnome.gio through its headers; this one consumes\n// the same package through its module, so both doors are checked from the\n// OUTSIDE — which is what an index example is for.\n//\n// ⚠️ THE TWO DOORS DO NOT COMPOSE, and that is GCC's rule rather than a choice\n// here. A TU that imports the module AND textually includes a glib header\n// reaches twice — once through the module's global fragment, once\n// directly — and the same `struct tm` from the same file becomes two entities:\n//\n// error: conflicting declaration 'struct tm'\n// note: previous declaration as 'struct tm' (of module gnome.glib)\n//\n// So a consumer picks ONE route, and which one is decided by macros: a module\n// cannot carry them, and glib's are half its API. Code using `G_DEFINE_TYPE`,\n// `G_OBJECT` or `G_TYPE_*` takes the header route. Code using the FUNCTION API\n// — most of gio, and much of glib — imports and includes nothing.\n#ifdef __linux__\n\nimport gnome.gio; // re-exports gnome.glib, gnome.gobject, gnome.gmodule\n\n#include \n#include \n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n std::printf(\"import gnome.gio\\n\\n\");\n\n // ⭐ THE SYMBOL pango WAS BLOCKED ON, through the module route.\n // G_TYPE_LIST_MODEL is a macro; g_list_model_get_type() is what it wraps.\n const GType lm = g_list_model_get_type();\n check(lm != 0, \"g_list_model_get_type() — the interface is registered\");\n check(std::strcmp(g_type_name(lm), \"GListModel\") == 0, \"…and it is GListModel\");\n\n // GFile: gio's function API needs no macros at all, which is the case the\n // module route is actually for.\n GFile *f = g_file_new_for_path(\"/etc/hostname\");\n char *base = g_file_get_basename(f);\n char *uri = g_file_get_uri(f);\n check(base && std::strcmp(base, \"hostname\") == 0, \"GFile: basename\");\n check(uri && std::strcmp(uri, \"file:///etc/hostname\") == 0, \"GFile: to URI\");\n g_free(base);\n g_free(uri);\n\n // A GListStore holding GFiles — g_file_get_type() rather than G_TYPE_FILE.\n GListStore *store = g_list_store_new(g_file_get_type());\n g_list_store_append(store, f);\n check(g_list_model_get_n_items(reinterpret_cast(store)) == 1,\n \"GListModel: one item after append\");\n g_object_unref(store);\n g_object_unref(f);\n\n // Streams, and the zlib converter — compat.zlib is a real dependency.\n const char plain[] = \"the module route needs no macros\";\n GInputStream *src = g_memory_input_stream_new_from_data(plain, sizeof plain - 1, nullptr);\n GConverter *cz = reinterpret_cast(\n g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB, -1));\n GInputStream *comp = g_converter_input_stream_new(src, cz);\n GConverter *dz = reinterpret_cast(\n g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB));\n GInputStream *back = g_converter_input_stream_new(comp, dz);\n char buf[128] = {};\n gsize got = 0;\n g_input_stream_read_all(back, buf, sizeof buf, &got, nullptr, nullptr);\n check(got == sizeof plain - 1 && std::memcmp(buf, plain, got) == 0,\n \"zlib: compress -> decompress round trip\");\n g_object_unref(back); g_object_unref(dz);\n g_object_unref(comp); g_object_unref(src); g_object_unref(cz);\n\n // D-Bus introspection, with no bus.\n static const char xml[] =\n \"\"\n \"\"\n \"\";\n GDBusNodeInfo *node = g_dbus_node_info_new_for_xml(xml, nullptr);\n check(node != nullptr, \"GDBus: introspection XML parses\");\n if (node) g_dbus_node_info_unref(node);\n\n // The three siblings arrived through gnome.gio, matching gio.h's includes.\n char *s = g_strdup(\"glib\"); // gnome.glib\n check(s && std::strcmp(s, \"glib\") == 0, \"g_strdup — gnome.glib re-exported\");\n g_free(s);\n check(g_object_get_type() != 0, \"g_object_get_type — gnome.gobject re-exported\");\n check(g_module_supported(), \"g_module_supported — gnome.gmodule re-exported\");\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n#endif\n" + } + ] + }, + { + "project": "gl", + "text": "# GL test project — the render half of the graphics stack.\n#\n# THIS MEMBER DECLARES ITS OWN [indices], and has to. The workspace root maps\n# `compat` to this checkout, and an index is looked up BY NAMESPACE, so\n# `freedesktop.*` would resolve against the published index rather than the\n# packages under test. A member-level table REPLACES the root's.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"gl-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# EGL plus ONE GL flavour, which is the only correct shape — and this member\n# used to name all three, which is how the reason was found.\n#\n# libGLESv2, libGLESv1_CM and libOpenGL are three FRONT ENDS onto the same\n# libGLdispatch, and their symbol sets overlap: `glClear` is exported by all\n# three. Link two of them and the name binds to whichever the loader mapped\n# first — measured here, `glClear` resolved to libGLESv1_CM.so.1 in a program\n# that meant GLESv2, silently and with nothing to warn about it. That is the\n# same class of ambiguity soname reuse creates, and it is avoided the same way:\n# pick one.\n#\n# A project picks the flavour it writes against. A Wayland compositor is GLESv2\n# (it takes a context through EGL and draws with shaders), so that is what this\n# member is. libGLESv1_CM and libOpenGL have their own tests in the fork, where\n# each is the only GL library in the process.\n[target.'cfg(linux)'.dependencies.freedesktop]\negl = \"1.7.0\"\nglesv2 = \"1.7.0\"\n\n# GBM is how a headless test gets a real context without a display server.\n[target.'cfg(linux)'.dependencies.compat]\nlibgbm = \"25.0.7\"\n", + "files": [ + { + "path": "tests/examples/gl/tests/gl.cpp", + "text": "// The render half of the graphics stack: EGL hands out a context, GLES2 draws.\n//\n// Every FUNCTION and TYPE below comes from a module — `import khronos.egl;`\n// and `import khronos.glesv2;`. The headers are included for the `EGL_*` and\n// `GL_*` CONSTANTS only, which are macros and cannot be exported by a module.\n//\n// ─────────────────────────────────────────────────────────────────────────\n// WHY THIS TEST EXISTS AND WHAT IT IS FOR\n//\n// \"libGLESv2 exports 358 symbols\" is not the claim worth making — a library\n// built from the wrong dispatch table exports about that many too. The claim\n// is that a program can get a context and DRAW, and the only honest way to\n// check it is to draw and read the pixel back.\n//\n// So the file has two halves:\n//\n// * assertions that hold on ANY machine, including a CI runner with no GPU:\n// the modules carry the API, the libraries are the ones this index built\n// rather than the ecosystem payload's same-soname copies, and all of them\n// route through ONE libGLdispatch;\n// * a real render, gated on a DRM device being present (MCPP_RUN_DRM_DEVICE),\n// because a runner has no /dev/dri at all.\n//\n// The gate is opt-in for the same reason compat.libdrm's test gates device\n// access: a machine without a GPU is not a defect in these packages.\n//\n// ─────────────────────────────────────────────────────────────────────────\n// THE PART THAT IS EASY TO GET FALSELY GREEN\n//\n// `xim:libglvnd` ships libEGL.so.1, libGLESv2.so.2, libOpenGL.so.0 and\n// libGLdispatch.so.0 under exactly these sonames. Only one library per soname\n// is ever mapped and nothing warns about the loser, so a test that merely calls\n// GL can pass while none of this index's builds are loaded. dladdr pins it.\n//\n// Note this is a CONSUMER, which is what makes that check meaningful here: the\n// fork's own in-package tests link the package's OBJECTS, so their binaries\n// define glClear themselves and dladdr reports the executable. Measured.\n\n#ifdef __linux__\n\n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n\nimport khronos.egl;\nimport khronos.glesv2;\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// dlsym rather than `&glClear`: taking the address of an imported function\n// yields the caller's PLT stub, so dladdr would report this executable.\nstd::string object_of(const char *symbol)\n{\n void *sym = ::dlsym(RTLD_DEFAULT, symbol);\n Dl_info info{};\n if (sym == nullptr || ::dladdr(sym, &info) == 0 || info.dli_fname == nullptr) {\n return {};\n }\n return info.dli_fname;\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. The modules carry the API ─────────────────────────────────────\n EGLDisplay none = EGL_NO_DISPLAY;\n GLenum err = GL_NO_ERROR;\n check(none == EGL_NO_DISPLAY && err == GL_NO_ERROR,\n \"the two modules' types and the headers' macros agree\");\n\n // ── 2. Each library is THIS index's build ────────────────────────────\n struct { const char *symbol; const char *library; } from[] = {\n {\"eglInitialize\", \"libEGL.so.1\"},\n {\"glClear\", \"libGLESv2.so.2\"},\n {\"__glDispatchMakeCurrent\", \"libGLdispatch.so.0\"},\n };\n for (auto &f : from) {\n const std::string where = object_of(f.symbol);\n check(!where.empty(), (std::string(\"dladdr locates \") + f.library).c_str());\n if (where.empty()) {\n continue;\n }\n std::printf(\" %-24s <- %s\\n\", f.symbol, where.c_str());\n check(where.find(\"xim-x-libglvnd\") == std::string::npos,\n \"…and it is not the ecosystem payload's copy\");\n }\n\n // ── 3. EGL and GLES2 share ONE dispatch ──────────────────────────────\n // This is what makes the family a family. If libEGL came from one build\n // and libGLESv2 from another, both would work in isolation and a context\n // made by one would dispatch through the other's table — the failure mode\n // GLVND's ABI version exists to catch at runtime rather than at build time.\n {\n const std::string a = object_of(\"eglInitialize\");\n const std::string b = object_of(\"glClear\");\n const std::string d = object_of(\"__glDispatchMakeCurrent\");\n check(!a.empty() && !b.empty() && !d.empty() && a != b,\n \"libEGL and libGLESv2 are distinct objects\");\n // Both must be reachable from the same build tree, and there must be\n // exactly one libGLdispatch — which is what the single `d` proves.\n check(!d.empty(), \"exactly one libGLdispatch answers for the process\");\n }\n\n // ── 4. ONE GL flavour, and the check that it is the right one ────────\n //\n // libGLESv2, libGLESv1_CM and libOpenGL are three front ends onto the same\n // dispatch and their symbol sets overlap — all three export `glClear`. An\n // earlier version of this member depended on all three, and `glClear`\n // resolved to libGLESv1_CM.so.1 in a program that meant GLESv2. Nothing\n // warned; the loader simply took whichever it had mapped first.\n //\n // The assertion has to be about ATTRIBUTION, not presence, and that is\n // worth spelling out because the obvious version does not work:\n // `dlsym(RTLD_DEFAULT, \"glMatrixMode\") == nullptr` looks like it would\n // prove GLESv1 is not linked, and it never fails — libGLdispatch.so.0\n // exports the FULL GL surface (measured: glMatrixMode is in it), because it\n // is the complete dispatch table and the per-flavour libraries are thin\n // front ends that select a subset of it.\n //\n // So the check is which OBJECT answers: `glClear` must come from the\n // flavour front end this project asked for, not from another one and not\n // from the dispatch underneath.\n {\n const std::string where = object_of(\"glClear\");\n std::printf(\" glClear resolves in: %s\\n\", where.c_str());\n check(where.find(\"libGLESv2.so\") != std::string::npos,\n \"glClear resolves in libGLESv2 — the flavour this project asked for\");\n }\n\n // ── 5. A real render, opt-in ─────────────────────────────────────────\n if (std::getenv(\"MCPP_RUN_DRM_DEVICE\") == nullptr) {\n std::puts(\"\\n (the render is opt-in: set MCPP_RUN_DRM_DEVICE=1 on a \"\n \"machine with /dev/dri)\");\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n }\n\n std::puts(\"\\n-- GBM device -> EGL context -> glClear -> glReadPixels --\");\n\n int fd = -1;\n for (const char *node : {\"/dev/dri/renderD128\", \"/dev/dri/card0\"}) {\n fd = ::open(node, O_RDWR);\n if (fd >= 0) {\n std::printf(\" node: %s\\n\", node);\n break;\n }\n }\n if (fd < 0) {\n std::puts(\" MCPP_RUN_DRM_DEVICE set but no DRM node opened; skipping\");\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n }\n\n gbm_device *gbm = gbm_create_device(fd);\n check(gbm != nullptr, \"gbm_create_device on a real node\");\n if (gbm == nullptr) {\n ::close(fd);\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n }\n\n EGLDisplay dpy = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm, nullptr);\n check(dpy != EGL_NO_DISPLAY, \"eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR)\");\n\n EGLint major = 0, minor = 0;\n check(dpy != EGL_NO_DISPLAY && eglInitialize(dpy, &major, &minor) == EGL_TRUE,\n \"eglInitialize\");\n if (dpy != EGL_NO_DISPLAY) {\n std::printf(\" EGL %d.%d, vendor %s\\n\", major, minor,\n eglQueryString(dpy, EGL_VENDOR));\n }\n\n // A SURFACELESS context, and that choice is not incidental.\n //\n // The obvious headless recipe — a pbuffer — does not work on GBM: measured,\n // `eglChooseConfig` with `EGL_SURFACE_TYPE = EGL_PBUFFER_BIT` finds nothing,\n // because GBM's surfaces come from `gbm_surface_create` and the platform\n // advertises no pbuffer configs at all. `EGL_KHR_surfaceless_context` is\n // what the platform does support, and it is also what a compositor uses\n // before it has anything to present to: a context with no default\n // framebuffer, rendering into an FBO.\n check(eglBindAPI(EGL_OPENGL_ES_API) == EGL_TRUE, \"eglBindAPI(EGL_OPENGL_ES_API)\");\n\n EGLint cfg_attrs[] = {\n EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,\n EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,\n EGL_NONE,\n };\n EGLConfig cfg{};\n EGLint n = 0;\n check(eglChooseConfig(dpy, cfg_attrs, &cfg, 1, &n) == EGL_TRUE && n > 0,\n \"eglChooseConfig found an ES2-renderable config\");\n\n EGLint ctx_attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};\n EGLContext ctx = eglCreateContext(dpy, cfg, EGL_NO_CONTEXT, ctx_attrs);\n check(ctx != EGL_NO_CONTEXT, \"eglCreateContext\");\n\n if (ctx != EGL_NO_CONTEXT) {\n check(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) == EGL_TRUE,\n \"eglMakeCurrent with no surface (EGL_KHR_surfaceless_context)\");\n\n // The FBO the surfaceless context renders into. This is the piece a\n // pbuffer would have provided.\n GLuint rb = 0, fbo = 0;\n glGenRenderbuffers(1, &rb);\n glBindRenderbuffer(GL_RENDERBUFFER, rb);\n glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 64, 64);\n glGenFramebuffers(1, &fbo);\n glBindFramebuffer(GL_FRAMEBUFFER, fbo);\n glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,\n GL_RENDERBUFFER, rb);\n check(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE,\n \"a 64x64 RGBA8 framebuffer is complete\");\n\n // THE ASSERTION THIS WHOLE FILE IS FOR. Everything above can pass on a\n // stack that cannot draw; this cannot.\n std::printf(\" GL_VERSION %s\\n\", glGetString(GL_VERSION));\n std::printf(\" GL_RENDERER %s\\n\", glGetString(GL_RENDERER));\n check(glGetString(GL_VERSION) != nullptr,\n \"glGetString(GL_VERSION) answers through the module\");\n\n glClearColor(0.25f, 0.5f, 0.75f, 1.0f);\n glClear(GL_COLOR_BUFFER_BIT);\n glFinish();\n\n unsigned char px[4] = {0, 0, 0, 0};\n glReadPixels(32, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px);\n std::printf(\" pixel read back: %u %u %u %u (wanted ~64 128 191 255)\\n\",\n px[0], px[1], px[2], px[3]);\n\n // One quantisation step of slack: the driver may round the float.\n auto near = [](unsigned char got, int want) {\n return got >= want - 2 && got <= want + 2;\n };\n check(near(px[0], 64) && near(px[1], 128) && near(px[2], 191) && near(px[3], 255),\n \"the pixel that came back is the colour that was cleared\");\n\n check(glGetError() == GL_NO_ERROR, \"no GL error along the way\");\n\n glDeleteFramebuffers(1, &fbo);\n glDeleteRenderbuffers(1, &rb);\n eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n }\n\n if (ctx != EGL_NO_CONTEXT) { eglDestroyContext(dpy, ctx); }\n if (dpy != EGL_NO_DISPLAY) { eglTerminate(dpy); }\n gbm_device_destroy(gbm);\n ::close(fd);\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "glad", + "text": "[package]\nname = \"glad-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nglad = \"0.0.0-651a425\"\n", + "files": [ + { + "path": "tests/examples/glad/tests/header.cpp", + "text": "// Smoke test — verify compat.glad header is usable + links\n#include \"glad/glad.h\"\nimport std;\n\nint main() {\n // Verify the header defines the expected types (no GL context needed)\n std::println(\"compat.glad smoke test: ok\");\n return 0;\n}\n" + } + ] + }, + { + "project": "glib", + "text": "# gnome.glib — the generated headers, and the subsystems that depend on them.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"glib-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\nglib = \"2.82.5\"\n", + "files": [ + { + "path": "tests/examples/glib/tests/glib.cpp", + "text": "// glib — the generated headers, and the subsystems that depend on them.\n//\n// WHAT THIS ASSERTS AND WHY\n//\n// glib is large and mostly self-evident: if GHashTable were broken the build\n// would not have got here. What is NOT self-evident in a fork is whether the\n// five generated headers say the right things, because every one of them was\n// written out by hand-reimplemented generators. A wrong answer in glibconfig.h\n// does not fail to compile — it changes the width of gsize, or the byte order,\n// or which printf glib calls.\n//\n// So each check reads back a value that a generator decided:\n//\n// glibconfig.h gsize/gssize widths, G_BYTE_ORDER, the format strings\n// gversionmacros.h GLIB_VERSION_2_82 exists and encodes 2.82\n// config.h USE_SYSTEM_PRINTF, via a format only glibc gets right\n// glib-visibility.h a GLIB_AVAILABLE_IN_2_x symbol is actually exported\n// pcre2 wiring GRegex, which is the one external dependency\n//\n// The subsystem checks that follow are chosen for the same reason: each is a\n// place where a wrong config.h answer would show up as a wrong RESULT rather\n// than a failure to build.\n\n#ifdef __linux__\n\n// ⚠️ NO extern \"C\" WRAPPER, and adding one BREAKS THIS UNDER libc++: glib\n// decorates every header with G_BEGIN_DECLS (which IS `extern \"C\" {`), and\n// glib.h pulls , which libc++ routes through — templates\n// inside an extern \"C\" block. `templates must have C++ linkage`, dozens of\n// times, against a header this test never names. libstdc++ does not, so gcc is\n// green and llvm is a wall. Wrap a C header only if it has none of its own.\n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. glibconfig.h: the type model ──────────────────────────────────\n std::printf(\" glib %d.%d.%d gsize=%zu gssize=%zu gpointer=%zu\\n\",\n glib_major_version, glib_minor_version, glib_micro_version,\n sizeof(gsize), sizeof(gssize), sizeof(gpointer));\n check(glib_major_version == 2 && glib_minor_version == 82,\n \"the runtime version matches the manifest\");\n check(sizeof(gsize) == sizeof(void *) && sizeof(gssize) == sizeof(void *),\n \"gsize and gssize are pointer-width, as glibconfig.h declares\");\n check(sizeof(gint32) == 4 && sizeof(gint64) == 8 && sizeof(gint16) == 2,\n \"the fixed-width types are the widths they are named for\");\n\n // G_BYTE_ORDER is written by the generator rather than probed, so it is\n // worth checking against something the machine can answer for itself.\n const guint32 probe = 0x01020304;\n const bool little = *reinterpret_cast(&probe) == 0x04;\n std::printf(\" G_BYTE_ORDER=%d measured=%s\\n\", G_BYTE_ORDER,\n little ? \"little\" : \"big\");\n check((G_BYTE_ORDER == G_LITTLE_ENDIAN) == little,\n \"G_BYTE_ORDER agrees with what the bytes actually say\");\n\n // The format strings are the half of glibconfig.h that a compile cannot\n // check: `%\" G_GSIZE_FORMAT \"` with the wrong modifier is undefined\n // behaviour that usually prints something plausible.\n {\n gchar *s = g_strdup_printf(\"%\" G_GSIZE_FORMAT, static_cast(123456789));\n std::printf(\" G_GSIZE_FORMAT renders %s\\n\", s);\n check(std::strcmp(s, \"123456789\") == 0,\n \"G_GSIZE_FORMAT prints a gsize correctly\");\n g_free(s);\n }\n\n // ── 2. gversionmacros.h ──────────────────────────────────────────────\n // The generator emits one block per even minor up to the current one.\n // Encoding is (major << 16) | (minor << 8) | micro.\n std::printf(\" GLIB_VERSION_2_82 = 0x%06x\\n\", GLIB_VERSION_2_82);\n check(GLIB_VERSION_2_82 == G_ENCODE_VERSION(2, 82),\n \"gversionmacros.h defines GLIB_VERSION_2_82 and encodes it correctly\");\n check(GLIB_VERSION_2_26 < GLIB_VERSION_2_82,\n \"…and the older ones too, in order\");\n\n // ── 3. config.h: USE_SYSTEM_PRINTF ───────────────────────────────────\n // Not directly observable — but %'d (thousands grouping) and the C99 %zu\n // are things glib's bundled gnulib printf implements differently from\n // glibc's. Rendering a long double in %La exercises the path that made the\n // link fail when the wrong printf was selected.\n {\n gchar *s = g_strdup_printf(\"%.3f|%05d|%s\", 1.5, 42, \"x\");\n std::printf(\" printf path renders %s\\n\", s);\n check(std::strcmp(s, \"1.500|00042|x\") == 0,\n \"g_strdup_printf goes through a working printf\");\n g_free(s);\n }\n\n // ── 4. glib-visibility.h ─────────────────────────────────────────────\n // g_pathbuf_init is GLIB_AVAILABLE_IN_2_76 — a symbol decorated by the\n // generated visibility header. Calling it proves the decoration expanded\n // to something that exports rather than hides.\n {\n GPathBuf pb;\n g_path_buf_init(&pb);\n g_path_buf_push(&pb, \"usr\");\n g_path_buf_push(&pb, \"share\");\n gchar *p = g_path_buf_to_path(&pb);\n std::printf(\" GPathBuf built %s\\n\", p ? p : \"(null)\");\n check(p != nullptr && std::strstr(p, \"usr/share\") != nullptr,\n \"a GLIB_AVAILABLE_IN_2_76 symbol is reachable\");\n g_free(p);\n g_path_buf_clear(&pb);\n }\n\n // ── 5. GRegex, i.e. the pcre2 dependency ─────────────────────────────\n // The only external library glib links here. \\p{Han} needs pcre2 built\n // with SUPPORT_UNICODE, which is compat.pcre2's decision — so this checks\n // the two packages agree, not merely that one of them built.\n {\n GError *err = nullptr;\n GRegex *re = g_regex_new(\"^\\\\p{Han}+$\", G_REGEX_DEFAULT,\n G_REGEX_MATCH_DEFAULT, &err);\n check(re != nullptr, \"g_regex_new compiles a Unicode property pattern\");\n if (re != nullptr) {\n check(g_regex_match(re, \"漢字\", G_REGEX_MATCH_DEFAULT, nullptr),\n \"…and it matches Han characters\");\n check(!g_regex_match(re, \"abc\", G_REGEX_MATCH_DEFAULT, nullptr),\n \"…and not Latin ones\");\n g_regex_unref(re);\n } else if (err != nullptr) {\n std::printf(\" %s\\n\", err->message);\n g_error_free(err);\n }\n }\n\n // ── 6. Unicode, the tables gunicode.h describes ──────────────────────\n check(g_unichar_type(0x6F22) == G_UNICODE_OTHER_LETTER,\n \"g_unichar_type classifies U+6F22 as a letter\");\n // The function is `g_unichar_get_script`, not `g_unichar_script`.\n check(g_unichar_get_script(0x6F22) == G_UNICODE_SCRIPT_HAN,\n \"…and g_unichar_get_script puts it in Han\");\n {\n gchar *up = g_utf8_strup(\"straße\", -1);\n std::printf(\" g_utf8_strup(straße) = %s\\n\", up);\n check(up != nullptr && std::strstr(up, \"STRASSE\") != nullptr,\n \"g_utf8_strup expands ß to SS, i.e. real case tables\");\n g_free(up);\n }\n\n // ── 7. the main loop and GVariant ────────────────────────────────────\n {\n GMainContext *ctx = g_main_context_new();\n check(ctx != nullptr, \"a GMainContext is created\");\n check(!g_main_context_iteration(ctx, FALSE),\n \"…and iterating it with nothing pending does not block\");\n g_main_context_unref(ctx);\n }\n {\n GVariant *v = g_variant_new(\"(si)\", \"hello\", 42);\n g_variant_ref_sink(v);\n const gchar *s = nullptr;\n gint i = 0;\n g_variant_get(v, \"(&si)\", &s, &i);\n std::printf(\" GVariant round-trip: %s %d\\n\", s, i);\n check(s != nullptr && std::strcmp(s, \"hello\") == 0 && i == 42,\n \"a GVariant tuple survives a serialise/parse round trip\");\n g_variant_unref(v);\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/glib/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. glib.cpp\n// next to this file takes the header route; this one takes the module, so both\n// doors are tested rather than assumed.\n//\n// ─────────────────────────────────────────────────────────────────────────\n// ⚠️ THE TWO DOORS DO NOT COMPOSE, AND THAT IS GCC'S RULE, NOT A CHOICE HERE.\n//\n// A TU that imports gnome.glib AND textually includes a glib header reaches\n// `` twice — once through the module's global fragment, once directly\n// — and the same `struct tm` from the same file becomes two entities:\n//\n// error: conflicting declaration 'struct tm'\n// note: previous declaration as 'struct tm' (of module gnome.glib)\n//\n// repeated for every type in it. So a consumer picks ONE route.\n//\n// WHAT THAT MEANS IN PRACTICE, stated plainly because it decides which route\n// to pick: a module cannot carry macros, and glib's macros are half its API\n// (1,337 `#define` against 1,312 declarations; for gio 1,679 against 1,753).\n// Code that uses `G_DEFINE_TYPE`, `G_OBJECT` or `G_TYPE_*` takes the header\n// route. Code that uses the FUNCTION API — most of gio, and a great deal of\n// glib — can import and include nothing at all, which is what this file does.\n#ifdef __linux__\n\nimport gnome.glib;\n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\"import gnome.glib — %d.%d.%d\\n\\n\",\n glib_major_version, glib_minor_version, glib_micro_version);\n check(glib_major_version == 2 && glib_minor_version == 82,\n \"the version variables come through the module\");\n\n // ── strings: GString and the g_str* family ───────────────────────────\n GString *s = g_string_new(\"import\");\n g_string_append(s, \" works\");\n check(std::strcmp(s->str, \"import works\") == 0, \"GString: new + append\");\n g_string_free(s, 1); // TRUE is a MACRO — not reachable through a module\n\n char *up = g_ascii_strup(\"mcpp\", -1);\n check(up && std::strcmp(up, \"MCPP\") == 0, \"g_ascii_strup\");\n g_free(up);\n\n // ── containers ───────────────────────────────────────────────────────\n GPtrArray *a = g_ptr_array_new();\n g_ptr_array_add(a, const_cast(\"one\"));\n g_ptr_array_add(a, const_cast(\"two\"));\n check(a->len == 2, \"GPtrArray: two elements\");\n g_ptr_array_free(a, 1);\n\n GHashTable *h = g_hash_table_new(g_str_hash, g_str_equal);\n g_hash_table_insert(h, const_cast(\"k\"), const_cast(\"v\"));\n const char *got = static_cast(g_hash_table_lookup(h, \"k\"));\n check(got && std::strcmp(got, \"v\") == 0, \"GHashTable: insert and look up\");\n g_hash_table_destroy(h);\n\n // ── an ENUMERATOR, reached through its typedef ───────────────────────\n // glib writes `typedef enum { … } GNormalizeMode;` — an UNNAMED enum\n // behind a typedef — and exporting the typedef is what makes the\n // enumerators visible. Nothing in the module names them individually.\n GNormalizeMode nfd = G_NORMALIZE_NFD, nfc = G_NORMALIZE_NFC;\n check(nfd != nfc, \"an enumerator arrives with its typedef\");\n\n // ── Unicode, which is where glib's own tables live ───────────────────\n check(g_unichar_isalpha(0x4E2D), \"g_unichar_isalpha on U+4E2D\");\n check(g_unichar_type(0x4E2D) == G_UNICODE_OTHER_LETTER, \"g_unichar_type\");\n\n // ── GVariant: parse and read back ────────────────────────────────────\n GVariant *v = g_variant_new_int32(42);\n check(g_variant_get_int32(v) == 42, \"GVariant: int32 round trip\");\n g_variant_unref(v);\n\n // ── the main loop ────────────────────────────────────────────────────\n GMainContext *ctx = g_main_context_new();\n check(ctx != nullptr, \"GMainContext: created\");\n g_main_context_unref(ctx);\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "glm", + "text": "# compat.glm test project. Header-only; the `compat` index redirect is\n# inherited from the workspace root.\n[package]\nname = \"glm-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nglm = \"1.0.2\"\n\n# The two switches a Vulkan consumer sets, declared HERE rather than in the\n# package: they change the meaning of the types crossing a library boundary, so\n# the package must not decide them for everyone. GLM_FORCE_DEPTH_ZERO_TO_ONE is\n# what makes glm::perspective produce Vulkan's 0..1 clip depth instead of\n# OpenGL's -1..1, and the test below asserts exactly that difference.\n[build]\ncxxflags = [\"-DGLM_FORCE_DEPTH_ZERO_TO_ONE\", \"-DGLM_ENABLE_EXPERIMENTAL\"]\n", + "files": [ + { + "path": "tests/examples/glm/tests/math.cpp", + "text": "// Behavioral test — compat.glm delivers a real glm, not an empty include root.\n//\n// The criterion is arithmetic, not compilation: a wrong include root fails to\n// compile, but a header set that resolved to the wrong thing would still need\n// to produce these numbers. Every value below is fixed by linear algebra or by\n// glm's documented conventions.\n#include \n#include \n#include \n\nimport std;\n\nint main() {\n // Cross product, right-handed: x cross y is z.\n const glm::vec3 x{1.0f, 0.0f, 0.0f};\n const glm::vec3 y{0.0f, 1.0f, 0.0f};\n if (glm::cross(x, y) != glm::vec3(0.0f, 0.0f, 1.0f)) {\n std::println(\"cross(x, y) is not z\");\n return 1;\n }\n\n // Column-major storage is part of glm's ABI, and it is what a consumer\n // relies on when it memcpy's a mat4 into a uniform buffer.\n glm::mat4 m{1.0f};\n m[3] = glm::vec4(2.0f, 3.0f, 4.0f, 1.0f);\n if (m[3][0] != 2.0f || m[3][1] != 3.0f || m[3][2] != 4.0f) {\n std::println(\"mat4 translation column is not where column-major puts it\");\n return 2;\n }\n const glm::vec4 moved = m * glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);\n if (moved != glm::vec4(3.0f, 4.0f, 5.0f, 1.0f)) {\n std::println(\"mat4 * vec4 did not translate\");\n return 3;\n }\n\n // GLM_FORCE_DEPTH_ZERO_TO_ONE, set by THIS project: with it, the near\n // plane maps to 0 (Vulkan). Without it glm would map it to -1 (OpenGL),\n // and this is the assertion that tells the two apart.\n const glm::mat4 proj = glm::perspective(glm::radians(45.0f), 16.0f / 9.0f, 0.1f, 100.0f);\n const glm::vec4 nearPoint = proj * glm::vec4(0.0f, 0.0f, -0.1f, 1.0f);\n const float ndcNear = nearPoint.z / nearPoint.w;\n if (std::abs(ndcNear) > 1e-4f) {\n std::println(\"near plane maps to {} — GLM_FORCE_DEPTH_ZERO_TO_ONE did not take\", ndcNear);\n return 4;\n }\n\n // gtx/, reached only because GLM_ENABLE_EXPERIMENTAL is defined.\n if (std::abs(glm::length2(glm::vec3(3.0f, 4.0f, 0.0f)) - 25.0f) > 1e-4f) {\n std::println(\"glm::length2 wrong\");\n return 5;\n }\n\n std::println(\"compat.glm: ok (cross/column-major/zero-to-one depth/gtx all correct)\");\n return 0;\n}\n" + } + ] + }, + { + "project": "gmodule", + "text": "# gnome.gmodule — dynamic loading, and the generated gmoduleconf.h behind it.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"gmodule-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\ngmodule = \"2.82.5\"\n", + "files": [ + { + "path": "tests/examples/gmodule/tests/gmodule.cpp", + "text": "// gmodule — dynamic loading, checked by actually loading something.\n//\n// WHAT THIS ASSERTS AND WHY\n//\n// gmodule is two source files, so \"did it compile\" is nearly free — and nearly\n// worthless. The value is in `gmoduleconf.h`, which build.mcpp generates and\n// which decides WHICH loader is compiled in. `G_MODULE_IMPL_NONE` produces a\n// library that builds, links, and returns \"dynamic loading not supported\" for\n// everything.\n//\n// So the checks are: does it claim to support loading, and does a load\n// actually work. `g_module_open(NULL)` opens the CURRENT EXECUTABLE — the one\n// case that needs no test fixture on disk — and a symbol looked up in it must\n// be one this binary really has.\n\n#ifdef __linux__\n\n// ⚠️ NO extern \"C\" WRAPPER, and adding one BREAKS THIS UNDER libc++: glib\n// decorates every header with G_BEGIN_DECLS (which IS `extern \"C\" {`), and\n// glib.h pulls , which libc++ routes through — templates\n// inside an extern \"C\" block. `templates must have C++ linkage`, dozens of\n// times, against a header this test never names. libstdc++ does not, so gcc is\n// green and llvm is a wall. Wrap a C header only if it has none of its own.\n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-56s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. gmoduleconf.h selected a real loader ──────────────────────────\n // G_MODULE_IMPL_NONE here would mean build.mcpp filled the template with\n // the \"no loader\" answer, and everything below would fail in a way that\n // reads like a broken system rather than a wrong generated header.\n std::printf(\" g_module_supported = %d, suffix = %s\\n\",\n g_module_supported(), G_MODULE_SUFFIX);\n check(g_module_supported(), \"g_module_supported — gmoduleconf.h chose the dl loader\");\n check(std::strcmp(G_MODULE_SUFFIX, \"so\") == 0,\n \"G_MODULE_SUFFIX is \\\"so\\\", from the generated glibconfig.h\");\n\n // ── 2. a load that needs no fixture ──────────────────────────────────\n // NULL means \"this executable\". It exercises the same dlopen path a plugin\n // would, without needing a .so to have been built alongside the test.\n GModule *self = g_module_open(nullptr, G_MODULE_BIND_LAZY);\n check(self != nullptr, \"g_module_open(NULL) opens the running executable\");\n if (self != nullptr) {\n // ⚠️ NO SYMBOL LOOKUP HERE, and the reason is a property of this\n // package rather than a gap in the test.\n //\n // `dlsym` on the main program sees only the DYNAMIC symbol table, which\n // needs `-rdynamic`. And gnome.glib is `kind = \"lib\"`: its objects are\n // merged into the consumer, so there is no libglib-2.0.so in the link\n // map either. Both halves of the obvious assertion — a symbol of this\n // executable's, or one of glib's — are therefore absent by\n // construction, and a test that asserted them would be measuring the\n // link line.\n //\n // What IS observable without a fixture on disk is that the loader\n // answers at all, and that it distinguishes present from absent.\n gpointer missing = nullptr;\n check(!g_module_symbol(self, \"mcpp_no_such_symbol_at_all\", &missing),\n \"a name that is not there is reported as absent, not crashed on\");\n\n std::printf(\" g_module_name = %s\\n\", g_module_name(self));\n check(g_module_close(self), \"g_module_close\");\n }\n\n // ── 2b. g_module_build_path — G_MODULE_SUFFIX in use ─────────────────\n // The one place the generated glibconfig.h value becomes a STRING a caller\n // sees. On Windows this would be \"plugin.dll\"; getting it wrong would make\n // every plugin host look for the wrong filename.\n {\n gchar *p = g_module_build_path(\"/opt/plug\", \"demo\");\n std::printf(\" g_module_build_path = %s\\n\", p ? p : \"(null)\");\n check(p != nullptr && std::strcmp(p, \"/opt/plug/libdemo.so\") == 0,\n \"g_module_build_path applies the lib prefix and .so suffix\");\n g_free(p);\n }\n\n // ── 3. the error path says something ─────────────────────────────────\n // G_MODULE_IMPL_NONE would also reach here, so this is checked after the\n // success case rather than instead of it.\n GModule *nope = g_module_open(\"/nonexistent/definitely-not-a-module.so\",\n G_MODULE_BIND_LAZY);\n check(nope == nullptr, \"opening a missing module fails\");\n const gchar *err = g_module_error();\n std::printf(\" g_module_error = %s\\n\", err ? err : \"(null)\");\n // G_MODULE_HAVE_DLERROR is one of the four values build.mcpp fills into\n // gmoduleconf.h; without it the message would be a generic placeholder\n // rather than the loader's own text.\n check(err != nullptr && std::strstr(err, \"definitely-not-a-module\") != nullptr,\n \"…and dlerror's message names the file — G_MODULE_HAVE_DLERROR is on\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/gmodule/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. The test\n// next to this file consumes gnome.gmodule through its headers; this one consumes\n// the same package through its module, so both doors are checked from the\n// OUTSIDE — which is what an index example is for.\n//\n// ⚠️ THE TWO DOORS DO NOT COMPOSE, and that is GCC's rule rather than a choice\n// here. A TU that imports the module AND textually includes a glib header\n// reaches twice — once through the module's global fragment, once\n// directly — and the same `struct tm` from the same file becomes two entities:\n//\n// error: conflicting declaration 'struct tm'\n// note: previous declaration as 'struct tm' (of module gnome.glib)\n//\n// So a consumer picks ONE route, and which one is decided by macros: a module\n// cannot carry them, and glib's are half its API. Code using `G_DEFINE_TYPE`,\n// `G_OBJECT` or `G_TYPE_*` takes the header route. Code using the FUNCTION API\n// — most of gio, and much of glib — imports and includes nothing.\n#ifdef __linux__\n\nimport gnome.gmodule; // re-exports gnome.glib\n\n#include \n#include \n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n std::printf(\"import gnome.gmodule\\n\\n\");\n\n check(g_module_supported(), \"g_module_supported() through the module\");\n\n // An enumerator, reached through its typedef — glib writes\n // `typedef enum { … } GModuleFlags;` and exporting the typedef is what\n // makes G_MODULE_BIND_LAZY visible. Nothing names it individually.\n GModuleFlags f = G_MODULE_BIND_LAZY;\n check(static_cast(f) == 1, \"an enumerator arrives with its typedef\");\n\n GModule *m = g_module_open(\"/nonexistent/definitely-not-a-module.so\", f);\n check(m == nullptr, \"opening a missing module fails\");\n const char *err = g_module_error();\n check(err != nullptr && std::strstr(err, \"definitely-not-a-module\") != nullptr,\n \"…and g_module_error names the file — the dl loader is wired up\");\n\n // ⭐ gnome.glib ARRIVES THROUGH gnome.gmodule, and it has to: glib is a\n // workspace PATH dependency, so a consumer that named it as well would get\n // error: dependency 'gnome.glib' is requested as both a version dep\n // and a path dep\n // gmodule.h includes glib.h, so the module re-exports gnome.glib to match.\n char *p = g_strdup(\"re-exported\");\n check(p != nullptr && std::strcmp(p, \"re-exported\") == 0,\n \"g_strdup — gnome.glib came through gnome.gmodule\");\n g_free(p);\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n#endif\n" + } + ] + }, + { + "project": "gmp-gmpxx", + "text": "# compat.gmp `gmpxx` FEATURE path — GMP's C++ bindings (mpz_class / mpq_class /\n# mpf_class, the RAII wrappers with operator overloading, and the iostream\n# operators).\n#\n# Its own member rather than a flag on tests/examples/gmp: features are resolved\n# per consuming project, so one project cannot hold compat.gmp both with and\n# without gmpxx, and the base member has to keep asserting the C-only build that\n# nearly every consumer takes.\n#\n# The negative half of the feature check is structural, the same shape as\n# tests/examples/catch2-main: `gmpxx.h` ships unconditionally (it is a header,\n# and headers cannot be gated), but the eleven .cc files behind\n# `operator<<(std::ostream&, const mpz_class&)` are the feature. Turn it off and\n# this member stops linking — measured: four undefined references, the ostream\n# operators for __mpz_struct / __mpq_struct / __mpf_struct and the istream one\n# for __mpz_struct. It does not quietly pass.\n#\n# ALL THREE PLATFORMS. The feature's TU is compiled by the CONSUMER's toolchain\n# precisely so that its std::ostream / std::string symbols match whichever C++\n# standard library is in play, which is a claim that is only worth anything if\n# every leg of the matrix actually links it.\n#\n# It inherits the workspace-root `compat` redirect, so the descriptor in THIS\n# checkout is what gets built and linked.\n[package]\nname = \"gmp-gmpxx-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngmp = { version = \"6.3.0\", features = [\"gmpxx\"] }\n", + "files": [ + { + "path": "tests/examples/gmp-gmpxx/tests/gmpxx.cpp", + "text": "// compat.gmp `gmpxx` feature: the C++ bindings compile against the consumer's\n// own toolchain and link.\n//\n// The stream operators are the load-bearing part. Everything else in gmpxx.h is\n// inline in the header, so a member that only did arithmetic would link with\n// the feature TURNED OFF and prove nothing. `os << mpz_class` resolves to\n// __gmp_ostream_operator, which lives in cxx/osmpz.cc — one of the eleven\n// sources the feature gates.\n//\n// It is also the one place where \"compiled by the consumer's toolchain\" is\n// actually tested: those symbols carry std::ostream / std::string in their\n// mangled names, so a prebuilt libgmpxx would resolve on libstdc++ and fail on\n// libc++.\n\n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\n\nint check_mpz_class() {\n // 128-bit-ish multiply, formatted through the ostream operator.\n mpz_class a(\"123456789012345678901234567890\");\n mpz_class b = a * a;\n std::ostringstream os;\n os << b;\n if (os.str() != \"15241578753238836750495351562536198787501905199875019052100\")\n return 20;\n\n // Operator overloading across mixed types, and the comparison operators.\n mpz_class c = a + 10;\n if (c - 10 != a) return 21;\n if (!(a < b) || !(b > a)) return 22;\n\n // Number-theoretic helpers reached through the C++ names.\n mpz_class g = gcd(mpz_class(123456789), mpz_class(987654321));\n if (g != 9) return 23;\n\n // get_str / set_str round trip via std::string.\n std::string hex = b.get_str(16);\n mpz_class back(hex, 16);\n if (back != b) return 24;\n\n // istream operator — the other half of the gated sources (cxx/ismpz.cc).\n std::istringstream is(\"999999999999999999999999999999\");\n mpz_class parsed;\n is >> parsed;\n if (parsed != mpz_class(\"999999999999999999999999999999\")) return 25;\n return 0;\n}\n\nint check_mpq_class() {\n mpq_class q(1, 3);\n q += mpq_class(1, 6); // 1/2\n q.canonicalize();\n std::ostringstream os;\n os << q;\n if (os.str() != \"1/2\") return 30;\n if (q.get_num() != 1 || q.get_den() != 2) return 31;\n\n mpq_class r = q * mpq_class(4, 3); // 2/3\n r.canonicalize();\n if (r != mpq_class(2, 3)) return 32;\n return 0;\n}\n\nint check_mpf_class() {\n mpf_class two(2, 256);\n mpf_class root = sqrt(two);\n if (!(root > 1.41 && root < 1.42)) return 40;\n // Squaring it back has to land within the working precision.\n mpf_class err = abs(root * root - 2);\n if (!(err < mpf_class(1e-30, 256))) return 41;\n\n std::ostringstream os;\n os << mpf_class(1.5, 64);\n if (os.str() != \"1.5\") return 42;\n return 0;\n}\n\n} // namespace\n\nint main() {\n int rc = check_mpz_class();\n if (rc == 0) rc = check_mpq_class();\n if (rc == 0) rc = check_mpf_class();\n if (rc != 0) {\n std::fprintf(stderr, \"compat.gmp gmpxx: check failed, code %d\\n\", rc);\n return rc;\n }\n std::cout << \"compat.gmp gmpxx: mpz_class/mpq_class/mpf_class + streams ok\"\n << std::endl;\n return 0;\n}\n" + } + ] + }, + { + "project": "gmp", + "text": "# compat.gmp — the DEFAULT (C API) build, on all three platforms.\n#\n# Not cfg-gated any more: compat.gmp no longer drives an autotools build\n# through an install() hook, it is described in `mcpp = { }` and compiled by\n# mcpp itself, so windows is a target like the others and a no-op main() there\n# would only hide that.\n#\n# The C++ bindings are the `gmpxx` feature and live in tests/examples/gmp-gmpxx:\n# features are resolved per consuming project, so one project cannot hold\n# compat.gmp both with and without them — and this member has to keep asserting\n# the build almost every consumer takes.\n#\n# It inherits the workspace-root `compat` redirect, so the descriptor in THIS\n# checkout is what gets built and linked.\n[package]\nname = \"gmp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngmp = \"6.3.0\"\n", + "files": [ + { + "path": "tests/examples/gmp/tests/gmp.cpp", + "text": "// compat.gmp end-to-end, C API: gmp.h resolves, the library links, and every\n// source directory the descriptor compiles actually works.\n//\n// One assertion group per directory in the package's source list, because a\n// build described by globs is exactly the kind that can lose a whole directory\n// without anything failing to compile:\n//\n// mpn/generic every group below (mpz/mpq/mpf all bottom out in mpn)\n// mpz factorial / powm / gcd / string round-trip / import-export\n// mpq rational arithmetic and canonicalisation\n// mpf the float layer, incl. mpf_sqrt\n// printf gmp_snprintf\n// scanf gmp_sscanf\n// rand gmp_randinit / mpz_urandomm\n//\n// The expected values are precomputed independently of GMP (Python\n// int/fractions), so a library that silently picked up a host libgmp, or one\n// built from the wrong tables, fails instead of agreeing with itself.\n\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\n// GMP allocates the result of mpz_get_str with ITS allocator, which is not\n// necessarily the one behind std::free. The documented way to give it back is\n// the free function GMP reports, and it is the only way that stays correct if a\n// consumer ever installs custom memory functions.\nvoid gmp_free(char* p) {\n if (p == nullptr) return;\n void (*freefunc)(void*, size_t) = nullptr;\n mp_get_memory_functions(nullptr, nullptr, &freefunc);\n freefunc(p, std::strlen(p) + 1);\n}\n\nint check_limb_shape() {\n // The descriptor vendors tables generated for limb=64/nail=0 and its\n // config.h refuses to compile without a 64-bit unsigned type. If gmp.h and\n // the compiled library ever disagreed about the limb — the LP64/LLP64\n // mistake a configure-baked header makes when it reaches Windows — every\n // other assertion here would still pass while mp_limb_t was half-width.\n if (GMP_LIMB_BITS != 64) return 10;\n if (sizeof(mp_limb_t) * 8 != GMP_LIMB_BITS) return 11;\n if (GMP_NAIL_BITS != 0) return 12;\n if (mp_bits_per_limb != GMP_LIMB_BITS) return 13;\n return 0;\n}\n\nint check_mpz() {\n // 100! — a known 158-digit value, compared digit by digit.\n {\n mpz_t f;\n mpz_init(f);\n mpz_fac_ui(f, 100);\n char* s = mpz_get_str(nullptr, 10, f);\n const char* expected =\n \"93326215443944152681699238856266700490715968264381621468592963895217599993229915\"\n \"608941463976156518286253697920827223758251185210916864000000000000000000000000\";\n int ok = s != nullptr && std::strcmp(s, expected) == 0;\n gmp_free(s);\n mpz_clear(f);\n if (!ok) return 20;\n }\n\n // Modular exponentiation, big enough to leave the single-limb paths:\n // 7^1234 mod (2^127 - 1).\n {\n mpz_t base, mod, res;\n mpz_init_set_ui(base, 7);\n mpz_init(mod);\n mpz_ui_pow_ui(mod, 2, 127);\n mpz_sub_ui(mod, mod, 1);\n mpz_init(res);\n mpz_powm_ui(res, base, 1234, mod);\n char* s = mpz_get_str(nullptr, 10, res);\n int ok = s != nullptr &&\n std::strcmp(s, \"55471114467826807814326117990010739464\") == 0;\n gmp_free(s);\n mpz_clears(base, mod, res, nullptr);\n if (!ok) return 21;\n }\n\n // gcd / lcm on a pair with a known factorisation.\n //\n // The lcm is compared against an mpz built from a decimal string, not\n // through mpz_cmp_ui: that takes `unsigned long`, which is 32 bits on\n // LLP64 (Windows), so 13548070123626141 would be silently truncated and\n // the assertion would fail on Windows only. Every *_ui entry point in\n // this file is therefore kept under 2^32.\n {\n mpz_t a, b, g, l, expect;\n mpz_init_set_ui(a, 123456789);\n mpz_init_set_ui(b, 987654321);\n mpz_init(g);\n mpz_init(l);\n mpz_init_set_str(expect, \"13548070123626141\", 10);\n mpz_gcd(g, a, b);\n mpz_lcm(l, a, b);\n int ok = mpz_cmp_ui(g, 9) == 0 && mpz_cmp(l, expect) == 0;\n mpz_clears(a, b, g, l, expect, nullptr);\n if (!ok) return 22;\n }\n\n // Base-16 round trip through a 256-bit value, and mpz_sizeinbase.\n {\n const char* hex = \"ffffffffffffffffffffffffffffffff\"\n \"fffffffffffffffffffffffefffffc2f\"; // secp256k1 p\n mpz_t p;\n mpz_init(p);\n if (mpz_set_str(p, hex, 16) != 0) { mpz_clear(p); return 23; }\n if (mpz_sizeinbase(p, 2) != 256) { mpz_clear(p); return 24; }\n char* back = mpz_get_str(nullptr, 16, p);\n int ok = back != nullptr && std::strcmp(back, hex) == 0;\n gmp_free(back);\n // It is prime, which walks probab_prime_p -> mpn_trialdiv -> the\n // vendored trialdivtab.h, and millerrabin -> mpz_powm.\n if (ok && mpz_probab_prime_p(p, 25) == 0) ok = 0;\n mpz_clear(p);\n if (!ok) return 25;\n }\n\n // import/export: the limb-order path, which is where a wrong\n // HAVE_LIMB_LITTLE_ENDIAN would show up.\n {\n const unsigned char be[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,\n 0xfe, 0xdc, 0xba, 0x98};\n mpz_t v;\n mpz_init(v);\n mpz_import(v, sizeof(be), 1 /*most significant word first*/, 1, 0, 0, be);\n char* s = mpz_get_str(nullptr, 16, v);\n int ok = s != nullptr && std::strcmp(s, \"123456789abcdeffedcba98\") == 0;\n gmp_free(s);\n unsigned char out[sizeof(be)] = {0};\n size_t count = 0;\n mpz_export(out, &count, 1, 1, 0, 0, v);\n if (count != sizeof(be) || std::memcmp(out, be, sizeof(be)) != 0) ok = 0;\n mpz_clear(v);\n if (!ok) return 26;\n }\n\n return 0;\n}\n\nint check_mpq() {\n mpq_t q, r;\n mpq_init(q);\n mpq_init(r);\n mpq_set_ui(q, 1, 2);\n mpq_set_ui(r, 1, 3);\n mpq_add(q, q, r); // 5/6\n int ok = mpq_cmp_ui(q, 5, 6) == 0;\n mpq_set_ui(r, 10, 4); // canonicalises to 5/2\n mpq_canonicalize(r);\n if (mpz_cmp_ui(mpq_numref(r), 5) != 0 || mpz_cmp_ui(mpq_denref(r), 2) != 0) ok = 0;\n mpq_div(q, q, r); // (5/6) / (5/2) = 1/3\n if (mpq_cmp_ui(q, 1, 3) != 0) ok = 0;\n mpq_clear(q);\n mpq_clear(r);\n return ok ? 0 : 30;\n}\n\nint check_mpf() {\n mpf_set_default_prec(256);\n mpf_t x, s, one;\n mpf_init_set_ui(x, 2);\n mpf_init(s);\n mpf_init_set_ui(one, 1);\n mpf_sqrt(s, x); // sqrt(2)\n int ok = mpf_cmp_d(s, 1.0) > 0 && mpf_cmp_d(s, 2.0) < 0;\n // s*s must be 2 to well within the working precision.\n mpf_mul(x, s, s);\n mpf_sub_ui(x, x, 2);\n mpf_abs(x, x);\n mpf_div_2exp(one, one, 200); // 2^-200\n if (mpf_cmp(x, one) >= 0) ok = 0;\n mpf_clears(x, s, one, nullptr);\n return ok ? 0 : 40;\n}\n\nint check_printf_scanf() {\n // printf/ — gmp_snprintf with the %Zd conversion.\n mpz_t v;\n mpz_init(v);\n mpz_ui_pow_ui(v, 3, 40); // 12157665459056928801\n char buf[64] = {0};\n int n = gmp_snprintf(buf, sizeof(buf), \"[%Zd]\", v);\n int ok = n == 22 && std::strcmp(buf, \"[12157665459056928801]\") == 0;\n mpz_clear(v);\n if (!ok) return 50;\n\n // scanf/ — the same value back out of a string.\n mpz_t w;\n mpz_init(w);\n if (gmp_sscanf(\"12157665459056928801\", \"%Zd\", w) != 1) { mpz_clear(w); return 51; }\n mpz_t expect;\n mpz_init(expect);\n mpz_ui_pow_ui(expect, 3, 40);\n ok = mpz_cmp(w, expect) == 0;\n mpz_clears(w, expect, nullptr);\n return ok ? 0 : 52;\n}\n\nint check_rand() {\n // rand/ — a seeded Mersenne Twister is deterministic, so this asserts the\n // generator runs AND that every draw lands in range.\n gmp_randstate_t st;\n gmp_randinit_mt(st);\n gmp_randseed_ui(st, 20260810u);\n mpz_t n, x;\n mpz_init(n);\n mpz_ui_pow_ui(n, 10, 30);\n mpz_init(x);\n int ok = 1;\n for (int i = 0; i < 64; ++i) {\n mpz_urandomm(x, st, n);\n if (mpz_sgn(x) < 0 || mpz_cmp(x, n) >= 0) { ok = 0; break; }\n }\n mpz_clears(n, x, nullptr);\n gmp_randclear(st);\n return ok ? 0 : 60;\n}\n\n} // namespace\n\nint main() {\n int rc = check_limb_shape();\n if (rc == 0) rc = check_mpz();\n if (rc == 0) rc = check_mpq();\n if (rc == 0) rc = check_mpf();\n if (rc == 0) rc = check_printf_scanf();\n if (rc == 0) rc = check_rand();\n if (rc != 0) {\n std::fprintf(stderr, \"compat.gmp: check failed, code %d\\n\", rc);\n return rc;\n }\n std::printf(\"compat.gmp %s: mpz/mpq/mpf/printf/scanf/rand ok (limb %d bits)\\n\",\n gmp_version, GMP_LIMB_BITS);\n return 0;\n}\n" + } + ] + }, + { + "project": "gobject", + "text": "# gnome.gobject — the type system, registered and exercised at run time.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"gobject-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\ngobject = \"2.82.5\"\n", + "files": [ + { + "path": "tests/examples/gobject/tests/gobject.cpp", + "text": "// gobject — the type system, exercised rather than merely linked.\n//\n// WHAT THIS ASSERTS AND WHY\n//\n// GObject is the kind of library where \"it linked\" proves nothing: GType is a\n// runtime registry, so a build that compiled half of it still links and then\n// fails the first time something registers a type. Every check below makes the\n// library DO something and reads back a value it computed:\n//\n// g_type_init-era registry a fundamental type's name, from gtype.c\n// the GENERATED enum types g_unicode_script_get_type(), which exists only\n// because build.mcpp reproduced glib-mkenums\n// a derived type registered at run time, with a property\n// properties set through GValue and read back\n// signals connected, emitted, and observed to have run\n// GBinding two objects kept in step, which is gbinding.c\n//\n// The enum-type check is the one that matters most for this fork: it is the\n// only evidence that the mkenums reimplementation produced a REGISTERABLE type\n// rather than a header that merely compiles.\n\n#ifdef __linux__\n\n// ⚠️ NO extern \"C\" WRAPPER, and adding one BREAKS THIS UNDER libc++.\n//\n// glib decorates every header with G_BEGIN_DECLS/G_END_DECLS, which IS\n// `extern \"C\" {`, so a wrapper is redundant. It is also harmful: glib.h pulls\n// and , and libc++ routes those through\n// /, which define TEMPLATES. Inside an extern \"C\" block that\n// is\n//\n// __type_traits/add_cv_quals.h:20: error: templates must have C++ linkage\n//\n// reported dozens of times against a standard-library header this test never\n// names. libstdc++ does not route them that way, so the gcc leg is green and\n// the llvm leg is a wall of errors — the same asymmetry that caught a missing\n// in the cairo fork.\n//\n// The rule: wrap a C header ONLY if it has no extern \"C\" of its own.\n// compat.libseat and freedesktop.libdisplay-info need it; glib does not.\n#include \n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// A minimal derived type, declared the way every GObject library does it.\n// Registering it exercises gtype.c, gobject.c, gparam.c and gvalue.c at once.\nstruct TestThing {\n GObject parent;\n gint answer;\n};\nstruct TestThingClass {\n GObjectClass parent;\n};\n\nenum { PROP_0, PROP_ANSWER, N_PROPS };\nGParamSpec *props[N_PROPS] = {};\n\nGType test_thing_get_type(void);\nG_DEFINE_TYPE(TestThing, test_thing, G_TYPE_OBJECT)\n\nvoid test_thing_set_property(GObject *o, guint id, const GValue *v, GParamSpec *p)\n{\n if (id == PROP_ANSWER) {\n reinterpret_cast(o)->answer = g_value_get_int(v);\n } else {\n G_OBJECT_WARN_INVALID_PROPERTY_ID(o, id, p);\n }\n}\n\nvoid test_thing_get_property(GObject *o, guint id, GValue *v, GParamSpec *p)\n{\n if (id == PROP_ANSWER) {\n g_value_set_int(v, reinterpret_cast(o)->answer);\n } else {\n G_OBJECT_WARN_INVALID_PROPERTY_ID(o, id, p);\n }\n}\n\nguint sig_changed = 0;\n\nvoid test_thing_class_init(TestThingClass *klass)\n{\n GObjectClass *oc = G_OBJECT_CLASS(klass);\n oc->set_property = test_thing_set_property;\n oc->get_property = test_thing_get_property;\n props[PROP_ANSWER] = g_param_spec_int(\"answer\", nullptr, nullptr,\n G_MININT, G_MAXINT, 0,\n static_cast(G_PARAM_READWRITE));\n g_object_class_install_properties(oc, N_PROPS, props);\n\n sig_changed = g_signal_new(\"changed\", test_thing_get_type(), G_SIGNAL_RUN_LAST,\n 0, nullptr, nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT);\n}\n\nvoid test_thing_init(TestThing *) {}\n\nint seen = -1;\nvoid on_changed(TestThing *, gint value, gpointer) { seen = value; }\n\n} // namespace\n\nint main()\n{\n // ── 1. the type registry answers about its own fundamentals ──────────\n std::printf(\" glib %d.%d.%d\\n\", glib_major_version, glib_minor_version,\n glib_micro_version);\n check(glib_major_version == 2 && glib_minor_version == 82,\n \"glib_*_version report 2.82, the version the manifest declares\");\n check(std::strcmp(g_type_name(G_TYPE_INT), \"gint\") == 0,\n \"g_type_name(G_TYPE_INT) is \\\"gint\\\" — the registry is initialised\");\n check(g_type_is_a(G_TYPE_OBJECT, G_TYPE_OBJECT), \"g_type_is_a on GObject itself\");\n\n // ── 2. THE GENERATED ENUM TYPES ──────────────────────────────────────\n // These exist only because build.mcpp reproduced glib-mkenums over\n // glib/gunicode.h. Registering one and reading a value back is the only\n // thing that proves the generator produced working code rather than a\n // header that happens to compile.\n const GType script = g_unicode_script_get_type();\n check(script != 0, \"g_unicode_script_get_type() registered a GType\");\n GEnumClass *ec = static_cast(g_type_class_ref(script));\n check(ec != nullptr, \"…and its class can be referenced\");\n if (ec != nullptr) {\n GEnumValue *v = g_enum_get_value(ec, G_UNICODE_SCRIPT_HAN);\n std::printf(\" G_UNICODE_SCRIPT_HAN = %s / %s\\n\",\n v ? v->value_name : \"(null)\", v ? v->value_nick : \"(null)\");\n check(v != nullptr && std::strcmp(v->value_name, \"G_UNICODE_SCRIPT_HAN\") == 0,\n \"…and G_UNICODE_SCRIPT_HAN is in it under its own name\");\n // The nick is mkenums' derived form: prefix stripped, lower case,\n // underscores to hyphens. Getting it right is the fiddly half of the\n // generator, so it is asserted rather than assumed.\n check(v != nullptr && std::strcmp(v->value_nick, \"han\") == 0,\n \"…with the nick mkenums derives, \\\"han\\\"\");\n g_type_class_unref(ec);\n }\n check(g_unicode_type_get_type() != 0 && g_unicode_break_type_get_type() != 0\n && g_normalize_mode_get_type() != 0,\n \"all four generated enum types register\");\n\n // ⭐ THE MACRO NAMES, WHICH ARE A DIFFERENT OUTPUT OF THE SAME GENERATOR.\n //\n // Everything above reads the FUNCTION and the NICK, and an earlier 2.82.5\n // tarball got both of those right while defining `G_UNICODE_TYPE_TYPE`\n // where upstream defines `G_TYPE_UNICODE_TYPE`. glib-mkenums has two\n // prefixes — one from the ENUMERATORS (drives nicks), one from the TYPE\n // NAME (drives this macro) — and conflating them changes only the macro.\n //\n // So it compiled, linked, and passed this very test. The four lines below\n // are what it would NOT have passed: naming the macro is a compile error\n // if it is spelled wrong, and it is the only spelling upstream has.\n check(G_TYPE_UNICODE_TYPE == g_unicode_type_get_type()\n && G_TYPE_UNICODE_SCRIPT == g_unicode_script_get_type()\n && G_TYPE_UNICODE_BREAK_TYPE == g_unicode_break_type_get_type()\n && G_TYPE_NORMALIZE_MODE == g_normalize_mode_get_type(),\n \"the G_TYPE_* macros are upstream's, not the enumerator prefix\");\n\n // ── 3. a derived type, its property, its signal ──────────────────────\n const GType thing = test_thing_get_type();\n check(thing != 0 && g_type_is_a(thing, G_TYPE_OBJECT),\n \"a derived type registers and is a GObject\");\n std::printf(\" registered %s\\n\", g_type_name(thing));\n\n GObject *o = static_cast(g_object_new(thing, \"answer\", 42, nullptr));\n check(o != nullptr, \"g_object_new with a construct property\");\n\n gint got = 0;\n g_object_get(o, \"answer\", &got, nullptr);\n std::printf(\" answer = %d\\n\", got);\n check(got == 42, \"…and g_object_get reads it back through GValue\");\n\n g_signal_connect(o, \"changed\", G_CALLBACK(on_changed), nullptr);\n g_signal_emit(o, sig_changed, 0, 7);\n std::printf(\" the handler saw %d\\n\", seen);\n check(seen == 7, \"a signal is emitted and the handler runs\");\n\n // ── 4. GBinding — gbinding.c, and a real use of the property system ──\n GObject *b = static_cast(g_object_new(thing, nullptr));\n g_object_bind_property(o, \"answer\", b, \"answer\", G_BINDING_DEFAULT);\n g_object_set(o, \"answer\", 99, nullptr);\n gint mirrored = 0;\n g_object_get(b, \"answer\", &mirrored, nullptr);\n std::printf(\" bound object followed to %d\\n\", mirrored);\n check(mirrored == 99, \"g_object_bind_property keeps two objects in step\");\n\n g_object_unref(b);\n g_object_unref(o);\n check(true, \"objects destroyed without a warning\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/gobject/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. The test\n// next to this file consumes gnome.gobject through its headers; this one consumes\n// the same package through its module, so both doors are checked from the\n// OUTSIDE — which is what an index example is for.\n//\n// ⚠️ THE TWO DOORS DO NOT COMPOSE, and that is GCC's rule rather than a choice\n// here. A TU that imports the module AND textually includes a glib header\n// reaches twice — once through the module's global fragment, once\n// directly — and the same `struct tm` from the same file becomes two entities:\n//\n// error: conflicting declaration 'struct tm'\n// note: previous declaration as 'struct tm' (of module gnome.glib)\n//\n// So a consumer picks ONE route, and which one is decided by macros: a module\n// cannot carry them, and glib's are half its API. Code using `G_DEFINE_TYPE`,\n// `G_OBJECT` or `G_TYPE_*` takes the header route. Code using the FUNCTION API\n// — most of gio, and much of glib — imports and includes nothing.\n#ifdef __linux__\n\nimport gnome.gobject; // re-exports gnome.glib\n\n#include \n#include \n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n std::printf(\"import gnome.gobject\\n\\n\");\n\n // ⚠️ EVERY G_TYPE_* IS A MACRO, so this file reaches the type registry\n // through the FUNCTIONS those macros wrap. That is not a workaround — it\n // is the shape of the module route, and it is why the header route stays\n // supported next door.\n const GType obj = g_object_get_type();\n check(obj != 0, \"g_object_get_type() — the registry answers\");\n check(std::strcmp(g_type_name(obj), \"GObject\") == 0, \"g_type_name(GObject)\");\n check(g_type_is_a(obj, obj), \"g_type_is_a on GObject itself\");\n\n // The four GTypes build.mcpp's glib-mkenums reimplementation produced.\n // ⭐ Their MACRO names are what shipped wrong once (G_UNICODE_TYPE_TYPE for\n // G_TYPE_UNICODE_TYPE) — but a macro cannot come through a module, so this\n // route checks the FUNCTIONS and the header route next door checks the\n // macros. Two different outputs of one generator, tested separately on\n // purpose.\n check(g_unicode_script_get_type() != 0 && g_unicode_type_get_type() != 0\n && g_unicode_break_type_get_type() != 0 && g_normalize_mode_get_type() != 0,\n \"the four generated enum GTypes register\");\n\n // GValue, entirely through functions.\n // G_VALUE_INIT is a MACRO; zero-initialising is what it expands to.\n GValue v = {};\n g_value_init(&v, g_type_from_name(\"gint\"));\n g_value_set_int(&v, 42);\n check(g_value_get_int(&v) == 42, \"GValue: set and get an int\");\n g_value_unset(&v);\n\n // A real object, created and reference-counted without a single macro.\n GObject *o = static_cast(g_object_new(obj, nullptr));\n check(o != nullptr, \"g_object_new(GObject)\");\n g_object_ref(o);\n g_object_unref(o);\n g_object_unref(o);\n\n // gnome.glib arrived through gnome.gobject, as glib-object.h -> glib.h does.\n char *s = g_strdup_printf(\"%d\", 7);\n check(s != nullptr && std::strcmp(s, \"7\") == 0,\n \"g_strdup_printf — gnome.glib came through gnome.gobject\");\n g_free(s);\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n#endif\n" + } + ] + }, + { + "project": "godot-cpp-module-v10", + "text": "# godotengine.godot-cpp-m 10.0.0-rc1 (Godot 4.6) module-layer test project.\n#\n# Separate member from godot-cpp-module for the same reason compat has two:\n# the versions are different bindings of different engine releases, and each\n# has to be built and asserted on its own.\n#\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `godotengine`. A member-level [indices] REPLACES the inherited table,\n# so the module package's own compat.godot-cpp dependency resolves from the\n# GLOBAL published index -- that descriptor is pre-merge-validated by\n# tests/examples/godot-cpp-v10.\n[indices]\ngodotengine = { path = \"../../..\" }\n\n[package]\nname = \"godot-cpp-module-v10-tests\"\nversion = \"0.1.0\"\n\n[dependencies]\ngodotengine.godot-cpp-m = \"10.0.0-rc1\"\n", + "files": [ + { + "path": "tests/examples/godot-cpp-module-v10/tests/godot_cpp_module_v10.cpp", + "text": "// Behavioral test for the godotengine.godot-cpp-m 10.0.0-rc1 module package (Godot 4.6): godot-cpp's\n// API reached with `import godot_cpp;` and no #include at all.\n//\n// The numbers are deliberately the same ones tests/examples/godot-cpp asserts\n// through headers -- that member is the header spelling of this one, so a\n// divergence here means the module re-export changed behaviour.\n//\n// Both halves can fail:\n// * Vector2::length(), Basis::orthonormalized(), Color::to_rgba32() and\n// AABB::get_volume() are DEFINED in compat.godot-cpp's src/variant/*.cpp,\n// so they only resolve if that dependency really compiled and linked.\n// * Node, Node2D, Node::PROCESS_MODE_*, godot::OK and Variant::OBJECT exist\n// only in the pre-generated gen/ tree.\n//\n// Not covered here, by nature: anything routed through the\n// gdextension_interface_* function pointers (String, Array, class\n// registration) needs a Godot process that has loaded the extension. The\n// GDCLASS/macro shape is covered by the package's own test suite.\n\nimport std;\nimport godot_cpp;\n\nnamespace {\n\nbool close(double a, double b) {\n return std::fabs(a - b) < 1e-5;\n}\n\n} // namespace\n\nint main() {\n using namespace godot;\n\n const bool vec2_ok = close(Vector2(3, 4).length(), 5.0) &&\n close(Vector2(3, 4).normalized().length(), 1.0);\n\n const Vector3 cross = Vector3(1, 0, 0).cross(Vector3(0, 1, 0));\n const bool vec3_ok = cross == Vector3(0, 0, 1) &&\n close(Vector3(2, 3, 6).length(), 7.0);\n\n const bool basis_ok = close(Basis().orthonormalized().determinant(), 1.0);\n\n const bool color_ok = Color(1.0f, 0.0f, 0.0f, 1.0f).to_rgba32() == 0xff0000ffu;\n\n const AABB box(Vector3(0, 0, 0), Vector3(2, 3, 4));\n const AABB other(Vector3(1, 1, 1), Vector3(4, 4, 4));\n const bool aabb_ok = close(box.get_volume(), 24.0) &&\n box.intersects(other) &&\n close(box.intersection(other).get_volume(), 6.0);\n\n // These bindings are Godot 4.6. The 4.5 member checks GODOT_VERSION_MAJOR\n // / _MINOR, but those are MACROS from version.hpp and a module cannot\n // export macros -- so the version is asserted through types instead:\n // EditorDock and AHashMap exist in 4.6 and not in 4.5.\n const bool version_ok = sizeof(EditorDock) > 0 &&\n sizeof(AHashMap) > 0;\n\n const bool gen_ok = sizeof(Node) > 0 &&\n sizeof(Node2D) > 0 &&\n Node::PROCESS_MODE_INHERIT == 0 &&\n Node::PROCESS_MODE_DISABLED == 4 &&\n godot::OK == 0 &&\n godot::ERR_FILE_NOT_FOUND == 7 &&\n godot::SIDE_LEFT == 0 &&\n Variant::OBJECT != Variant::NIL;\n\n const bool math_ok = Math::is_equal_approx(1.0f, 1.0f) &&\n !Math::is_zero_approx(1.0f) &&\n close(Math::lerp(0.0, 10.0, 0.25), 2.5);\n\n const bool ok = version_ok && vec2_ok && vec3_ok && basis_ok && color_ok &&\n aabb_ok && gen_ok && math_ok;\n std::println(\"version={} vec2={} vec3={} basis={} color={} aabb={} gen={} math={}\",\n version_ok, vec2_ok, vec3_ok, basis_ok, color_ok, aabb_ok, gen_ok, math_ok);\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "godot-cpp-module", + "text": "# godotengine.godot-cpp (module layer) test project: consumes `import godot_cpp;`\n# and asserts the same numbers the compat.godot-cpp member asserts through\n# headers -- if the module re-export changed behaviour, these would move.\n#\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `godotengine`. A member-level [indices] REPLACES the inherited table\n# rather than merging with it, which is what keeps this to ONE project index\n# repo -- two repos pointing at the same tree make every lookup ambiguous.\n#\n# The module package's own transitive dep (compat.godot-cpp) therefore resolves\n# from the GLOBAL published index, not from this checkout; the compat\n# descriptor is pre-merge-validated by its own member (tests/examples/godot-cpp).\n[indices]\ngodotengine = { path = \"../../..\" }\n\n[package]\nname = \"godot-cpp-module-tests\"\nversion = \"0.1.0\"\n\n# Dotted spelling on purpose: CI maps a changed pkgs//.lua to the\n# members whose mcpp.toml mentions , and here is the whole\n# `godotengine.godot-cpp-m`.\n[dependencies]\ngodotengine.godot-cpp-m = \"4.5.0\"\n", + "files": [ + { + "path": "tests/examples/godot-cpp-module/tests/godot_cpp_module.cpp", + "text": "// Behavioral test for the godotengine.godot-cpp-m module package: godot-cpp's\n// API reached with `import godot_cpp;` and no #include at all.\n//\n// The numbers are deliberately the same ones tests/examples/godot-cpp asserts\n// through headers -- that member is the header spelling of this one, so a\n// divergence here means the module re-export changed behaviour.\n//\n// Both halves can fail:\n// * Vector2::length(), Basis::orthonormalized(), Color::to_rgba32() and\n// AABB::get_volume() are DEFINED in compat.godot-cpp's src/variant/*.cpp,\n// so they only resolve if that dependency really compiled and linked.\n// * Node, Node2D, Node::PROCESS_MODE_*, godot::OK and Variant::OBJECT exist\n// only in the pre-generated gen/ tree.\n//\n// Not covered here, by nature: anything routed through the\n// gdextension_interface_* function pointers (String, Array, class\n// registration) needs a Godot process that has loaded the extension. The\n// GDCLASS/macro shape is covered by the package's own test suite.\n\nimport std;\nimport godot_cpp;\n\nnamespace {\n\nbool close(double a, double b) {\n return std::fabs(a - b) < 1e-5;\n}\n\n} // namespace\n\nint main() {\n using namespace godot;\n\n const bool vec2_ok = close(Vector2(3, 4).length(), 5.0) &&\n close(Vector2(3, 4).normalized().length(), 1.0);\n\n const Vector3 cross = Vector3(1, 0, 0).cross(Vector3(0, 1, 0));\n const bool vec3_ok = cross == Vector3(0, 0, 1) &&\n close(Vector3(2, 3, 6).length(), 7.0);\n\n const bool basis_ok = close(Basis().orthonormalized().determinant(), 1.0);\n\n const bool color_ok = Color(1.0f, 0.0f, 0.0f, 1.0f).to_rgba32() == 0xff0000ffu;\n\n const AABB box(Vector3(0, 0, 0), Vector3(2, 3, 4));\n const AABB other(Vector3(1, 1, 1), Vector3(4, 4, 4));\n const bool aabb_ok = close(box.get_volume(), 24.0) &&\n box.intersects(other) &&\n close(box.intersection(other).get_volume(), 6.0);\n\n const bool gen_ok = sizeof(Node) > 0 &&\n sizeof(Node2D) > 0 &&\n Node::PROCESS_MODE_INHERIT == 0 &&\n Node::PROCESS_MODE_DISABLED == 4 &&\n godot::OK == 0 &&\n godot::ERR_FILE_NOT_FOUND == 7 &&\n godot::SIDE_LEFT == 0 &&\n Variant::OBJECT != Variant::NIL;\n\n const bool math_ok = Math::is_equal_approx(1.0f, 1.0f) &&\n !Math::is_zero_approx(1.0f) &&\n close(Math::lerp(0.0, 10.0, 0.25), 2.5);\n\n const bool ok = vec2_ok && vec3_ok && basis_ok && color_ok && aabb_ok &&\n gen_ok && math_ok;\n std::println(\"vec2={} vec3={} basis={} color={} aabb={} gen={} math={}\",\n vec2_ok, vec3_ok, basis_ok, color_ok, aabb_ok, gen_ok, math_ok);\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "godot-cpp-v10", + "text": "# compat.godot-cpp 10.0.0-rc1 (Godot 4.6) test project.\n#\n# Separate member rather than a second dependency in the 4.5 one: the two\n# versions are different bindings of different engine releases, and each has to\n# be built and asserted on its own -- same shape as catch2 / catch2-v2.\n#\n# `compat` is redirected to this checkout by the workspace-root [indices],\n# which every member inherits.\n\n[package]\nname = \"godot-cpp-v10-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngodot-cpp = \"10.0.0-rc1\"\n", + "files": [ + { + "path": "tests/examples/godot-cpp-v10/tests/godot_cpp_v10.cpp", + "text": "// Behavioral test for compat.godot-cpp 10.0.0-rc1 -- godot-cpp's own 10.x\n// version line, whose bindings target Godot 4.6.\n//\n// The point of a second member is that it can tell the two versions apart, so\n// the first thing asserted is WHICH bindings arrived: the generated version\n// header and a class that exists in 4.6 and not in 4.5. The rest mirrors the\n// 4.5 member so a behavioural difference between the two would show up as a\n// diff between two otherwise identical tests.\n//\n// As there, everything asserted is pure math or compile-time: anything routed\n// through the gdextension_interface_* pointers needs a Godot process that has\n// loaded the extension.\n\n#include // new in Godot 4.6\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n\nnamespace {\n\nbool close(double a, double b) {\n return std::fabs(a - b) < 1e-5;\n}\n\n} // namespace\n\nusing namespace godot;\n\n// A GDCLASS subclass with bound methods -- the shape every GDExtension is\n// written in, and the one that breaks first on a new platform: without\n// TYPED_METHOD_BIND, ClassDB::bind_method casts member pointers through a\n// forward-declared class, which the MSVC ABI rejects outright. Compiling and\n// linking this is the assertion; it is never CALLED, because ClassDB and\n// StringName go through the gdextension_interface_* pointers, which are null\n// outside a Godot process that has loaded the extension.\nclass TestSprite : public Node {\n GDCLASS(TestSprite, Node)\n\nprotected:\n static void _bind_methods() {\n ClassDB::bind_method(D_METHOD(\"get_speed\"), &TestSprite::get_speed);\n ClassDB::bind_method(D_METHOD(\"set_speed\", \"speed\"), &TestSprite::set_speed);\n }\n\npublic:\n double get_speed() const { return speed; }\n void set_speed(double p_speed) { speed = p_speed; }\n\nprivate:\n double speed = 1.0;\n};\n\nint main() {\n // these bindings are Godot 4.6, not the 4.5 the sibling member pins\n const bool version_ok = GODOT_VERSION_MAJOR == 4 &&\n GODOT_VERSION_MINOR == 6 &&\n sizeof(EditorDock) > 0;\n\n // out-of-line variant math: proves the library linked\n const bool vec2_ok = close(Vector2(3, 4).length(), 5.0) &&\n close(Vector2(3, 4).normalized().length(), 1.0);\n\n const Vector3 cross = Vector3(1, 0, 0).cross(Vector3(0, 1, 0));\n const bool vec3_ok = cross == Vector3(0, 0, 1) &&\n close(Vector3(2, 3, 6).length(), 7.0);\n\n const bool basis_ok = close(Basis().orthonormalized().determinant(), 1.0);\n\n const bool color_ok = Color(1.0f, 0.0f, 0.0f, 1.0f).to_rgba32() == 0xff0000ffu;\n\n const AABB box(Vector3(0, 0, 0), Vector3(2, 3, 4));\n const AABB other(Vector3(1, 1, 1), Vector3(4, 4, 4));\n const bool aabb_ok = close(box.get_volume(), 24.0) &&\n box.intersects(other) &&\n close(box.intersection(other).get_volume(), 6.0);\n\n const bool gen_ok = sizeof(Node) > 0 &&\n Node::PROCESS_MODE_INHERIT == 0 &&\n Node::PROCESS_MODE_DISABLED == 4 &&\n godot::OK == 0 &&\n godot::ERR_FILE_NOT_FOUND == 7 &&\n Variant::OBJECT != Variant::NIL;\n\n // ODR-use what GDCLASS generated without calling into the engine\n const StringName &(*class_name_fn)() = &TestSprite::get_class_static;\n void (*init_fn)() = &TestSprite::initialize_class;\n const bool bind_ok = class_name_fn != nullptr && init_fn != nullptr &&\n std::is_base_of::value;\n\n const bool ok = bind_ok && version_ok && vec2_ok && vec3_ok && basis_ok && color_ok &&\n aabb_ok && gen_ok;\n std::printf(\"version=%d bind=%d vec2=%d vec3=%d basis=%d color=%d aabb=%d gen=%d\\n\",\n version_ok, bind_ok, vec2_ok, vec3_ok, basis_ok, color_ok, aabb_ok, gen_ok);\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "godot-cpp", + "text": "# compat.godot-cpp test project: builds the pre-generated GDExtension bindings\n# and asserts on the part of the API that runs without a Godot process --\n# the Variant math types, whose out-of-line definitions live in the library's\n# src/variant/*.cpp, so a passing run proves the ~1000-TU library really did\n# compile and link.\n#\n# `compat` is redirected to this checkout by the workspace-root [indices],\n# which every member inherits.\n\n[package]\nname = \"godot-cpp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngodot-cpp = \"4.5.0\"\n", + "files": [ + { + "path": "tests/examples/godot-cpp/tests/godot_cpp.cpp", + "text": "// Behavioral test for compat.godot-cpp.\n//\n// Two things are under test, and both can fail:\n//\n// 1. The PRE-GENERATED bindings are really in the package. gen/include is\n// what upstream's binding_generator.py would have produced at build time;\n// if it were missing, and the global\n// constants below would not compile at all.\n// 2. The library links. Vector2::length(), Basis::orthonormalized(),\n// Color::to_rgba32() and AABB::get_volume() are declared in the headers\n// but DEFINED in src/variant/*.cpp, so these calls only resolve if the\n// ~1000 translation units of the package were compiled and linked in.\n//\n// Everything asserted here is pure math -- no gdextension_interface function\n// pointers, so no running Godot process is needed. Engine classes are checked\n// by compiling against them (types, enums, sizes), which is all that can be\n// done outside a loaded extension.\n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n\nnamespace {\n\nbool close(double a, double b) {\n return std::fabs(a - b) < 1e-5;\n}\n\n} // namespace\n\nusing namespace godot;\n\n// A GDCLASS subclass with bound methods -- the shape every GDExtension is\n// written in, and the one that breaks first on a new platform: without\n// TYPED_METHOD_BIND, ClassDB::bind_method casts member pointers through a\n// forward-declared class, which the MSVC ABI rejects outright. Compiling and\n// linking this is the assertion; it is never CALLED, because ClassDB and\n// StringName go through the gdextension_interface_* pointers, which are null\n// outside a Godot process that has loaded the extension.\nclass TestSprite : public Node {\n GDCLASS(TestSprite, Node)\n\nprotected:\n static void _bind_methods() {\n ClassDB::bind_method(D_METHOD(\"get_speed\"), &TestSprite::get_speed);\n ClassDB::bind_method(D_METHOD(\"set_speed\", \"speed\"), &TestSprite::set_speed);\n }\n\npublic:\n double get_speed() const { return speed; }\n void set_speed(double p_speed) { speed = p_speed; }\n\nprivate:\n double speed = 1.0;\n};\n\nint main() {\n // --- out-of-line variant math: proves the library linked ---\n const bool vec2_ok = close(Vector2(3, 4).length(), 5.0) &&\n close(Vector2(3, 4).normalized().length(), 1.0);\n\n const Vector3 cross = Vector3(1, 0, 0).cross(Vector3(0, 1, 0));\n const bool vec3_ok = cross == Vector3(0, 0, 1) &&\n close(Vector3(2, 3, 6).length(), 7.0);\n\n const Basis basis = Basis().orthonormalized();\n const bool basis_ok = close(basis.determinant(), 1.0);\n\n const bool color_ok = Color(1.0f, 0.0f, 0.0f, 1.0f).to_rgba32() == 0xff0000ffu;\n\n const AABB box(Vector3(0, 0, 0), Vector3(2, 3, 4));\n const AABB other(Vector3(1, 1, 1), Vector3(4, 4, 4));\n const bool aabb_ok = close(box.get_volume(), 24.0) &&\n box.intersects(other) &&\n close(box.intersection(other).get_volume(), 1.0 * 2.0 * 3.0);\n\n // --- generated bindings: engine class + global enums are visible ---\n const bool gen_ok = sizeof(Node) > 0 &&\n Node::PROCESS_MODE_INHERIT == 0 &&\n Node::PROCESS_MODE_DISABLED == 4 &&\n godot::OK == 0 &&\n godot::ERR_FILE_NOT_FOUND == 7 &&\n godot::SIDE_LEFT == 0 &&\n Variant::OBJECT != Variant::NIL;\n\n // ODR-use what GDCLASS generated without calling into the engine\n const StringName &(*class_name_fn)() = &TestSprite::get_class_static;\n void (*init_fn)() = &TestSprite::initialize_class;\n const bool bind_ok = class_name_fn != nullptr && init_fn != nullptr &&\n std::is_base_of::value;\n\n const bool ok = bind_ok && vec2_ok && vec3_ok && basis_ok && color_ok && aabb_ok && gen_ok;\n std::printf(\"bind=%d vec2=%d vec3=%d basis=%d color=%d aabb=%d gen=%d\\n\",\n bind_ok, vec2_ok, vec3_ok, basis_ok, color_ok, aabb_ok, gen_ok);\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "grpc-codegen", + "text": "# grpc-codegen test project: the ONE-DEPENDENCY form of gRPC codegen.\n#\n# Its sibling tests/examples/grpc-module covers the module surface with no\n# codegen at all. This member covers what that one's header says is impossible:\n#\n# \"NO protoc output anywhere: gRPC's codegen needs host tools mcpp cannot\n# hand a consumer\"\n#\n# That stopped being true in mcpp 2026.8.6.2. `features = [\"codegen\"]` makes\n# the grpc package re-export protoc, grpc_cpp_plugin and the grpcgen rule to\n# whoever depends on it, so this manifest names ONE package and build.mcpp is\n# one line — and the .proto below is compiled from a stub that did not exist\n# when the build started.\n#\n# What this member protects, and what no other member can:\n# * `reexport = true` really reaches a consumer through the published index\n# (every other test of it is a path dependency in mcpp's own e2e);\n# * `rerun_if_changed_glob` really drives `generate_all()` — the file list is\n# never written down anywhere;\n# * the host tools are built for the BUILD machine and produce stubs that\n# link against the runtime this project links, which is the property the\n# whole design exists to make inexpressible-to-get-wrong.\n#\n# linux + macOS only, for the same reason grpc-module is: compat.openssl has no\n# windows xpm entry, so on windows this member carries no dependency and the\n# test compiles to a no-op main().\n[package]\nname = \"grpc-codegen-tests\"\nversion = \"0.1.0\"\n\n# `cfg(unix)`, not one block per OS: the condition is \"everywhere gRPC\n# resolves\", and what excludes windows is compat.openssl having no xpm entry\n# there — a single fact, so a single predicate. mcpp evaluates `unix` as\n# family == \"unix\" (src/build/prepare.cppm, alongside any()/all()/not()).\n[target.'cfg(unix)'.dependencies.grpc]\ngrpc = { version = \"1.83.0\", features = [\"codegen\"] }\n\n[target.'cfg(unix)'.build]\ncxxflags = [\"-DHAVE_GRPC=1\"]\n\n# windows carries the RULE but not gRPC itself, so build.mcpp compiles (its\n# `import grpcgen;` cannot be conditional) and then returns early. grpcgen is a\n# pure C++23 rule package — no openssl, no platform-limited dependency — which\n# is why it is available here while grpc is not.\n[target.'cfg(windows)'.dependencies.mcpplibs]\ngrpcgen = { version = \"1.83.0\", host-module = true }\n", + "files": [ + { + "path": "tests/examples/grpc-codegen/tests/codegen.cpp", + "text": "// Asserts the generated stubs exist, compile and carry the SERVICE — a\n// protobuf-only run would produce echo.pb.* and no Echo::Service, so a\n// half-configured codegen cannot pass this.\n//\n// HAVE_GRPC comes from this project's own cfg-gated cxxflags: the package is\n// linux/macOS-only, so elsewhere this file is an empty main().\n#ifdef HAVE_GRPC\n\n#include \n#include \n\n#include \"echo.pb.h\"\n#include \"echo.grpc.pb.h\"\n\nint main() {\n // The message half: a real generated type, round-tripped.\n echotest::EchoRequest req;\n req.set_text(\"mcpp\");\n std::string wire;\n if (!req.SerializeToString(&wire)) return 1;\n echotest::EchoRequest back;\n if (!back.ParseFromString(wire)) return 1;\n if (back.text() != \"mcpp\") return 1;\n\n // The SERVICE half — this is what grpc_cpp_plugin produced, and what a\n // protoc-only run would not have.\n const std::string method = echotest::Echo::service_full_name();\n std::printf(\"service = %s\\n\", method.c_str());\n if (method != \"echotest.Echo\") return 1;\n\n std::printf(\"grpc-codegen: OK\\n\");\n return 0;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "grpc-module", + "text": "# grpc test project: consumes the mcpplibs.grpc Form-A package (grpc-m) and\n# asserts behavior under `mcpp test`.\n#\n# The `default` namespace redirect points the built-in mcpplibs namespace at\n# this checkout, so the descriptor under test is pkgs/g/grpc.lua — not whatever\n# the published index happens to hold. grpc's own compat.* dependencies still\n# resolve from the published index, which is the point: this member proves the\n# descriptor works against the real dependency set.\n#\n# NO protoc output anywhere: this member exercises the MODULE surface. Codegen\n# is a separate member, tests/examples/grpc-codegen, which declares one\n# dependency and lets the grpc package hand it the whole toolchain.\n#\n# (This used to read \"gRPC's codegen needs host tools mcpp cannot hand a\n# consumer\". That stopped being true in mcpp 2026.8.6.2 — `reexport = true`\n# is exactly the mechanism for handing them over.)\n[package]\nname = \"grpc-module-tests\"\nversion = \"0.1.0\"\n\n# unix only: the package has no windows xpm entry (its compat.openssl\n# dependency has none yet), so on windows this member carries no dependency at\n# all and the test compiles to a no-op main() — the same shape\n# tests/examples/openssl uses for the same underlying reason.\n#\n# One `cfg(unix)` rather than a linux block and an identical macos block: the\n# condition is a single fact (\"gRPC resolves everywhere except windows\"), so it\n# should be written once. mcpp evaluates `unix` as family == \"unix\"\n# (src/build/prepare.cppm, alongside any()/all()/not()).\n[target.'cfg(unix)'.dependencies.grpc]\ngrpc = \"1.83.0\"\n\n[target.'cfg(unix)'.build]\ncxxflags = [\"-DHAVE_GRPC=1\"]\n\n[indices]\ngrpc = { path = \"../../..\" }\n", + "files": [ + { + "path": "tests/examples/grpc-module/tests/module.cpp", + "text": "// Behavioral test for the mcpplibs.grpc package, driven entirely through\n// `import grpc;` — there is no include in this file. Every call\n// reaches a real symbol in the linked gRPC, so a package that resolved and\n// \"built\" but linked nothing could not pass.\n//\n// Note the ORDER: textual #includes first, `import grpc;` last. The module\n// carries the standard library in its BMI, so a std header included after the\n// import arrives twice and GCC fails on std::string's operator==.\n// HAVE_GRPC comes from this project's own cfg-gated cxxflags — the package is\n// linux/macOS-only (see mcpp.toml), so elsewhere this file is an empty main().\n#ifdef HAVE_GRPC\n\n#include \n#include \n\nimport grpc;\n\nint main() {\n // A version string read out of the library, not a header constant.\n const std::string version = grpc::Version();\n std::printf(\"grpc::Version() = %s\\n\", version.c_str());\n if (version.rfind(\"1.83\", 0) != 0) return 1;\n\n // grpc::Status, error path included — an always-OK stub cannot pass.\n const grpc::Status bad(grpc::StatusCode::UNAVAILABLE, \"down\");\n if (bad.ok() || bad.error_code() != grpc::StatusCode::UNAVAILABLE ||\n bad.error_message() != \"down\" || !grpc::Status::OK.ok()) {\n return 1;\n }\n\n // A real channel object. Channels connect lazily, so no server is needed;\n // IDLE before the first RPC proves this is a live object from the library.\n auto channel = grpc::CreateChannel(\"127.0.0.1:1\", grpc::InsecureChannelCredentials());\n if (!channel) return 1;\n if (channel->GetState(false) != GRPC_CHANNEL_IDLE) return 1;\n\n std::puts(\"grpc module: OK\");\n return 0;\n}\n\n#else // !HAVE_GRPC\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "gtl", + "text": "# gtl test project: exercise the two container families people actually reach\n# for -- the Swiss-table flat_hash_map and the ordered btree_set.\n[package]\nname = \"gtl-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngtl = \"1.2.0\"\n", + "files": [ + { + "path": "tests/examples/gtl/tests/containers.cpp", + "text": "// compat.gtl — exercise the two container families that are the reason to pull\n// gtl in: the Swiss-table flat_hash_map and the ordered btree_set. Every check\n// asserts observable behaviour (contents, ordering, erasure), not just that the\n// headers compile.\n#include \n#include \nimport std;\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n // ---- flat_hash_map ---------------------------------------------------\n gtl::flat_hash_map m;\n for (int i = 0; i < 1000; ++i) m.emplace(std::format(\"k{}\", i), i);\n check(m.size() == 1000, \"flat_hash_map size after 1000 emplaces\");\n check(m.at(\"k0\") == 0 && m.at(\"k999\") == 999, \"lookup returns what was stored\");\n check(m.find(\"absent\") == m.end(), \"miss returns end()\");\n\n // Rehashing must not lose or corrupt entries.\n long sum = 0;\n for (const auto& [k, v] : m) sum += v;\n check(sum == 999L * 1000 / 2, \"every value survives rehashing\");\n\n check(m.erase(\"k500\") == 1, \"erase reports one removal\");\n check(m.erase(\"k500\") == 0, \"second erase reports none\");\n check(m.size() == 999, \"size after erase\");\n check(!m.contains(\"k500\"), \"erased key is gone\");\n\n // ---- parallel_flat_hash_map -----------------------------------------\n // Same API, internally sharded. Worth touching because it is gtl's\n // headline container and instantiates a different code path.\n gtl::parallel_flat_hash_map pm;\n for (int i = 0; i < 500; ++i) pm[i] = i * i;\n check(pm.size() == 500, \"parallel map size\");\n check(pm[20] == 400, \"parallel map value\");\n\n // ---- btree_set -------------------------------------------------------\n // Unlike the hash maps, this one is ORDERED -- assert that.\n gtl::btree_set s;\n for (int i : {42, 7, 99, 1, 63, 7}) s.insert(i);\n check(s.size() == 5, \"btree_set drops the duplicate\");\n check(std::ranges::is_sorted(s), \"btree_set iterates in order\");\n check(*s.begin() == 1 && *s.rbegin() == 99, \"btree_set endpoints\");\n check(*s.lower_bound(50) == 63, \"btree_set lower_bound\");\n\n if (ok) std::println(\"gtl OK\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "gui-stack", + "text": "# Linux GUI runtime stack: glfw + the X11/XCB libraries. These are Linux-only;\n# under cfg(linux) the deps are pulled and the tests assert symbol linkage +\n# headless behavior. Off-Linux the test files compile to no-op main() (the deps\n# aren't pulled), so `mcpp test --workspace` is a clean no-op on macOS/Windows.\n[package]\nname = \"gui-stack-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nglfw = \"3.4.0.1\"\nx11 = \"1.8.13\"\nxcb = \"1.17.0\"\nxext = \"1.3.7\"\nxrender = \"0.9.12\"\nxfixes = \"6.0.2\"\nxcursor = \"1.2.3\"\nxinerama = \"1.1.6\"\nxrandr = \"1.5.5\"\nxi = \"1.8.3\"\nxau = \"1.0.12\"\nxdmcp = \"1.1.5\"\n", + "files": [ + { + "path": "tests/examples/gui-stack/tests/glfw.cpp", + "text": "#ifdef __linux__\n#include \nint main() {\n glfwSetErrorCallback(nullptr);\n if (glfwInit()) glfwTerminate(); // tolerant: headless CI has no display\n return GLFW_VERSION_MAJOR == 3 ? 0 : 1;\n}\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/gui-stack/tests/x11.cpp", + "text": "#ifdef __linux__\n#include \n#include \nint main() {\n const KeySym escape = XStringToKeysym(\"Escape\");\n return X_PROTOCOL == 11 && escape == XK_Escape ? 0 : 1;\n}\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/gui-stack/tests/xcb.cpp", + "text": "#ifdef __linux__\n#include \n#include \nint main() {\n char* host = nullptr; int display = -1, screen = -1;\n const int ok = xcb_parse_display(\":0.1\", &host, &display, &screen);\n std::free(host);\n return ok && display == 0 && screen == 1 ? 0 : 1;\n}\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/gui-stack/tests/xlibs.cpp", + "text": "#ifdef __linux__\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nextern \"C\" int XextCreateExtension(void);\nint main() {\n return XextCreateExtension != nullptr && XRenderQueryExtension != nullptr &&\n XFixesQueryExtension != nullptr && XcursorLibraryPath != nullptr &&\n XineramaQueryExtension != nullptr && XRRQueryExtension != nullptr &&\n XIQueryVersion != nullptr ? 0 : 1;\n}\n#else\nint main() { return 0; }\n#endif\n" + }, + { + "path": "tests/examples/gui-stack/tests/xorg.cpp", + "text": "#ifdef __linux__\n#include \n#include \nint main() {\n ARRAY8 array{};\n const int allocated = XdmcpAllocARRAY8(&array, 1);\n if (allocated) XdmcpDisposeARRAY8(&array);\n char* auth_file = XauFileName();\n return allocated && auth_file != nullptr ? 0 : 1;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "gzip-hpp", + "text": "# compat.gzip-hpp test project. Header-only over zlib: the package's dependency\n# on compat.zlib is what makes resolve and the deflate symbols link,\n# and this member declares only gzip-hpp so that edge is exercised\n# transitively. `compat` redirect inherited from the workspace root.\n[package]\nname = \"gzip-hpp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ngzip-hpp = \"0.1.0\"\n", + "files": [ + { + "path": "tests/examples/gzip-hpp/tests/roundtrip.cpp", + "text": "// Behavioral test — compat.gzip-hpp compresses and decompresses through zlib.\n//\n// Round-trip alone would pass on a library that did nothing, so the test also\n// asserts the gzip MAGIC on the compressed bytes and that a compressible\n// payload actually shrinks. Both are answers only real deflate can give.\n//\n// It is also the link-time proof for the package's zlib dependency: every\n// function called here bottoms out in `deflate`/`inflate`.\n#include \n#include \n#include \n\n#include \n#include \n\nint main() {\n std::string original;\n for (int i = 0; i < 200; ++i) original += \"mcpp-index gzip round trip \";\n\n const std::string compressed = gzip::compress(original.data(), original.size());\n\n if (!gzip::is_compressed(compressed.data(), compressed.size())) {\n std::printf(\"gzip::is_compressed says the output is not gzip data\\n\");\n return 1;\n }\n // 0x1f 0x8b is the gzip magic; a wrapper that silently passed bytes\n // through would fail here rather than at the round trip.\n if (compressed.size() < 2 ||\n static_cast(compressed[0]) != 0x1f ||\n static_cast(compressed[1]) != 0x8b) {\n std::printf(\"compressed output has no gzip magic\\n\");\n return 2;\n }\n if (compressed.size() >= original.size()) {\n std::printf(\"highly repetitive input did not shrink: %zu -> %zu\\n\",\n original.size(), compressed.size());\n return 3;\n }\n\n const std::string restored = gzip::decompress(compressed.data(), compressed.size());\n if (restored != original) {\n std::printf(\"round trip differs (%zu vs %zu bytes)\\n\", restored.size(), original.size());\n return 4;\n }\n\n std::printf(\"compat.gzip-hpp: ok (%zu -> %zu -> %zu bytes, gzip magic present)\\n\",\n original.size(), compressed.size(), restored.size());\n return 0;\n}\n" + } + ] + }, + { + "project": "harfbuzz", + "text": "# HarfBuzz test project: shape a real buffer and assert the FreeType bridge is\n# linked in, under `mcpp test`.\n#\n# compat.freetype arrives transitively through compat.harfbuzz's own deps, so\n# it is not repeated here -- the test includes , whose FT_* parameter\n# types come from that transitive include path. If the propagation ever broke,\n# this member would fail to compile, which is the point.\n[package]\nname = \"harfbuzz-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nharfbuzz = \"14.3.0\"\n", + "files": [ + { + "path": "tests/examples/harfbuzz/tests/shape.cpp", + "text": "// compat.harfbuzz — assert the shaper actually runs, and that the FreeType\n// backend is compiled in rather than merely declared.\n//\n// Shaping needs no font file: hb_font_get_empty() gives a font whose glyphs\n// are all .notdef, which is enough to prove the buffer pipeline (itemize →\n// shape → read back infos/positions) is wired and returns one glyph per input\n// codepoint. That is a real assertion -- if hb-ot-shape.cc had not been\n// compiled into the amalgamation, glyph_count would be 0.\n#include \n#include \nimport std;\n\nint main() {\n bool ok = true;\n\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n check(hb_version_string() != nullptr, \"hb_version_string returned null\");\n\n unsigned major = 0, minor = 0, micro = 0;\n hb_version(&major, &minor, µ);\n check(major >= 14, \"unexpected HarfBuzz major version\");\n\n // Shape a short ASCII run through the empty font.\n hb_buffer_t* buf = hb_buffer_create();\n check(hb_buffer_allocation_successful(buf), \"hb_buffer_create failed\");\n\n static constexpr std::string_view text = \"mcpp\";\n hb_buffer_add_utf8(buf, text.data(), static_cast(text.size()), 0,\n static_cast(text.size()));\n hb_buffer_set_direction(buf, HB_DIRECTION_LTR);\n hb_buffer_set_script(buf, HB_SCRIPT_LATIN);\n hb_buffer_set_language(buf, hb_language_from_string(\"en\", -1));\n\n hb_shape(hb_font_get_empty(), buf, nullptr, 0);\n\n unsigned glyph_count = 0;\n hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buf, &glyph_count);\n hb_glyph_position_t* pos = hb_buffer_get_glyph_positions(buf, &glyph_count);\n\n check(glyph_count == text.size(), \"shaped glyph count != input length\");\n check(infos != nullptr && pos != nullptr, \"shaped buffer has no infos/positions\");\n if (infos != nullptr) {\n // The empty font maps everything to .notdef (glyph 0); cluster values\n // must still track the original byte offsets.\n for (unsigned i = 0; i < glyph_count; ++i) {\n check(infos[i].cluster == i, \"cluster does not track input offset\");\n }\n }\n\n hb_buffer_destroy(buf);\n\n // The FreeType bridge: taking its address proves hb-ft.cc was compiled in.\n // Without -DHAVE_FREETYPE the header still declares this and the build\n // fails at LINK time, which is exactly what this catches.\n check(reinterpret_cast(&hb_ft_face_create_referenced) != nullptr,\n \"hb_ft_face_create_referenced missing (FreeType backend not built)\");\n\n std::println(\"compat.harfbuzz smoke test: {} (hb {})\", ok ? \"ok\" : \"FAILED\",\n hb_version_string());\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "httplib-brotli", + "text": "[package]\nname = \"httplib-brotli-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nhttplib = { version = \"0.53.1\", features = [\"brotli\"] }\n", + "files": [ + { + "path": "tests/examples/httplib-brotli/tests/brotli.cpp", + "text": "#ifndef CPPHTTPLIB_BROTLI_SUPPORT\n#error \"compat.httplib[brotli] must define CPPHTTPLIB_BROTLI_SUPPORT in consumers\"\n#endif\n\n#define MCPP_HTTPLIB_FEATURE_NAME \"brotli\"\n#define MCPP_HTTPLIB_ENCODING \"br\"\n#include \"../../../fixtures/httplib_compression_case.hpp\"\n" + } + ] + }, + { + "project": "httplib-no-exceptions", + "text": "[package]\nname = \"httplib-no-exceptions-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nhttplib = { version = \"0.53.1\", features = [\"no-exceptions\"] }\n", + "files": [ + { + "path": "tests/examples/httplib-no-exceptions/tests/no_exceptions.cpp", + "text": "#ifndef CPPHTTPLIB_NO_EXCEPTIONS\n#error \"compat.httplib[no-exceptions] must define CPPHTTPLIB_NO_EXCEPTIONS in consumers\"\n#endif\n\n#include \n\n#include \n\nint main() {\n // Without CPPHTTPLIB_NO_EXCEPTIONS this constructor throws for an invalid\n // scheme. The feature changes it into a normal invalid client result.\n httplib::Client invalid(\"ftp://127.0.0.1:1\");\n if (invalid.is_valid()) return 1;\n\n httplib::Headers headers;\n const char *prohibited = httplib::detail::get_header_value(\n headers, \"REMOTE_ADDR\", \"missing\", 0);\n return prohibited != nullptr && prohibited[0] == '\\0' ? 0 : 2;\n}\n" + } + ] + }, + { + "project": "httplib-tls", + "text": "[package]\nname = \"httplib-tls-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nhttplib = { version = \"0.53.1\", features = [\"tls\"] }\n", + "files": [ + { + "path": "tests/examples/httplib-tls/tests/tls.cpp", + "text": "#ifndef CPPHTTPLIB_OPENSSL_SUPPORT\n#error \"compat.httplib[tls] must define CPPHTTPLIB_OPENSSL_SUPPORT in consumers\"\n#endif\n\n#include \n\n#include \"../../../fixtures/httplib_localhost_pem.hpp\"\n\n#include \n#include \n#include \n#include \n\nint main() {\n httplib::SSLServer::PemMemory pem{\n kHttplibCertificatePem,\n std::strlen(kHttplibCertificatePem),\n kHttplibPrivateKeyPem,\n std::strlen(kHttplibPrivateKeyPem),\n nullptr,\n 0,\n nullptr,\n };\n httplib::SSLServer server(pem);\n if (!server.is_valid()) return 1;\n\n const std::string expected = \"certificate-validated HTTPS response\";\n std::atomic handled{false};\n server.Get(\"/secure\", [&](const httplib::Request &req, httplib::Response &res) {\n handled = req.method == \"GET\" && req.path == \"/secure\";\n res.set_content(expected, \"text/plain\");\n });\n\n const int port = server.bind_to_any_port(\"127.0.0.1\");\n if (port <= 0) return 2;\n std::thread server_thread([&] { server.listen_after_bind(); });\n\n httplib::SSLClient client(\"localhost\", port);\n client.enable_system_ca(false);\n client.load_ca_cert_store(kHttplibCertificatePem,\n std::strlen(kHttplibCertificatePem));\n client.set_connection_timeout(8, 0);\n client.set_read_timeout(8, 0);\n const auto result = client.Get(\"/secure\");\n\n const bool ok = result && result->status == httplib::StatusCode::OK_200 &&\n result->get_header_value(\"Content-Type\") == \"text/plain\" &&\n result->body == expected && handled.load();\n\n server.stop();\n server_thread.join();\n return ok ? 0 : 3;\n}\n" + } + ] + }, + { + "project": "httplib-zlib", + "text": "[package]\nname = \"httplib-zlib-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nhttplib = { version = \"0.53.1\", features = [\"zlib\"] }\n", + "files": [ + { + "path": "tests/examples/httplib-zlib/tests/zlib.cpp", + "text": "#ifndef CPPHTTPLIB_ZLIB_SUPPORT\n#error \"compat.httplib[zlib] must define CPPHTTPLIB_ZLIB_SUPPORT in consumers\"\n#endif\n\n#define MCPP_HTTPLIB_FEATURE_NAME \"zlib\"\n#define MCPP_HTTPLIB_ENCODING \"gzip\"\n#include \"../../../fixtures/httplib_compression_case.hpp\"\n" + } + ] + }, + { + "project": "httplib-zstd", + "text": "[package]\nname = \"httplib-zstd-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nhttplib = { version = \"0.53.1\", features = [\"zstd\"] }\n", + "files": [ + { + "path": "tests/examples/httplib-zstd/tests/zstd.cpp", + "text": "#ifndef CPPHTTPLIB_ZSTD_SUPPORT\n#error \"compat.httplib[zstd] must define CPPHTTPLIB_ZSTD_SUPPORT in consumers\"\n#endif\n\n#define MCPP_HTTPLIB_FEATURE_NAME \"zstd\"\n#define MCPP_HTTPLIB_ENCODING \"zstd\"\n#include \"../../../fixtures/httplib_compression_case.hpp\"\n" + } + ] + }, + { + "project": "httplib", + "text": "[package]\nname = \"httplib-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nhttplib = \"0.53.1\"\n", + "files": [ + { + "path": "tests/examples/httplib/tests/http.cpp", + "text": "#include \n\n#include \n#include \n#include \n\n#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) || defined(CPPHTTPLIB_ZLIB_SUPPORT) || \\\n defined(CPPHTTPLIB_BROTLI_SUPPORT) || defined(CPPHTTPLIB_ZSTD_SUPPORT) || \\\n defined(CPPHTTPLIB_NO_EXCEPTIONS)\n#error \"compat.httplib enables no optional feature by default\"\n#endif\n\nint main() {\n const std::string expected = R\"({\"package\":\"compat.httplib\",\"ok\":true})\";\n std::atomic handled{false};\n\n httplib::Server server;\n server.Get(\"/status\", [&](const httplib::Request &req, httplib::Response &res) {\n handled = req.method == \"GET\" && req.path == \"/status\";\n res.set_content(expected, \"application/json\");\n });\n\n const int port = server.bind_to_any_port(\"127.0.0.1\");\n if (port <= 0) return 1;\n\n std::thread server_thread([&] { server.listen_after_bind(); });\n\n httplib::Client client(\"127.0.0.1\", port);\n client.set_connection_timeout(5, 0);\n client.set_read_timeout(5, 0);\n const auto result = client.Get(\"/status\");\n\n const bool ok = result && result->status == httplib::StatusCode::OK_200 &&\n result->get_header_value(\"Content-Type\") == \"application/json\" &&\n result->body == expected && handled.load();\n\n server.stop();\n server_thread.join();\n return ok ? 0 : 2;\n}\n" + } + ] + }, + { + "project": "huxerui-module", + "text": "# A member-level redirect replaces the workspace root's compat redirect, so\n# this test resolves huxerui.huxerui from the current checkout.\n[indices]\nhuxerui = { path = \"../../..\" }\n\n[package]\nname = \"huxerui-module-tests\"\nversion = \"0.1.0\"\n# C++20, because the DEPENDENCY says so and mcpp applies the ROOT's standard to\n# the whole module graph -- upstream's mcpp.toml spells that out:\n#\n# standard = \"c++20\"\n# # The SDK ABI baseline. Do not raise it: mcpp applies the root standard\n# # to the whole module graph, so this is imposed on every consumer too.\n#\n# Leaving this unset defaulted the member to c++23 and compiled huxerui's own\n# sources at c++23, above the baseline it declares. On windows that is not\n# academic: clang 20.1.7 against MSVC STL 14.51 then instantiates\n# `std::_Find_vectorized` and dies on `static_assert(false, \"unexpected size\")`\n# in . `import std;` still works here -- huxerui is\n# `language = \"c++20\", import_std = true`.\nstandard = \"c++20\"\n\n[dependencies.huxerui]\nhuxerui = \"0.3.0\"\n", + "files": [ + { + "path": "tests/examples/huxerui-module/tests/runtime.cpp", + "text": "// HuxerUI is a GUI framework, and this test opens no window: a CI runner has\n// no display, and the point here is that the PACKAGE resolves, builds and\n// links, not that GTK can paint.\n//\n// The last assertion is the one that carries that weight. Rect/Color are\n// header-attached entities that `import huxerui;` re-exports, so on their own\n// they would still compile if libhuxerui had never been built.\n// FlatLightThemeSpec/FlatDarkThemeSpec are out-of-line definitions living in\n// the library itself, so reaching them is what proves the link.\nimport std;\nimport huxerui;\n\nusing huxerui::Color;\nusing huxerui::Point;\nusing huxerui::Rect;\n\nint main() {\n const Rect bounds{10.0F, 20.0F, 100.0F, 50.0F};\n const bool inside = bounds.Contains(Point{60.0F, 40.0F});\n const bool outside = !bounds.Contains(Point{5.0F, 40.0F});\n\n // Rgba32 decodes 0xRRGGBBAA, so this is opaque orange.\n const Color orange = Color::Rgba32(0xFF8000FFU);\n const bool decoded = orange == Color::Rgb(255, 128, 0);\n\n const bool themes_differ =\n !(huxerui::FlatLightThemeSpec() == huxerui::FlatDarkThemeSpec());\n\n return (inside && outside && decoded && themes_differ) ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "imgui-module", + "text": "# Public `imgui` C++23-module package test (bare-name consumption from the\n# builtin default namespace — resolved against THIS checkout via the\n# workspace-root `[indices]` redirect, which every member inherits). Replaces the\n# former tests/smoke_imgui_module.sh reseeding driver. Linux-gated to the\n# validated surface: the package descriptor covers three platforms, but only\n# linux has ever been exercised by index CI; off-Linux the test compiles to a\n# no-op main(). Toolchain: workspace default (gcc@16.1.0) — the old smoke's\n# llvm@20.1.7 pin predates gcc16 module support; imgui-m upstream CI builds\n# the module layer with gcc@16.1.0.\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `default`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nocornut = { path = \"../../..\" }\n\n[package]\nname = \"imgui-module-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.ocornut]\nimgui = \"1.92.8\"\n", + "files": [ + { + "path": "tests/examples/imgui-module/tests/imgui_module.cpp", + "text": "// Public imgui module package end-to-end assertion: import-only consumption\n// (imgui.core + glfw_opengl3 backend), context + font atlas + one frame,\n// software-only (no window). Linux-only (see mcpp.toml).\n#ifdef __linux__\nimport std;\nimport imgui.core;\nimport imgui.backend.glfw_opengl3;\n\nint main() {\n auto init = &ImGui::Backend::GlfwOpenGL3::Init;\n auto shutdown = &ImGui::Backend::GlfwOpenGL3::Shutdown;\n if (init == nullptr || shutdown == nullptr) {\n return 1;\n }\n\n ImGuiContext* context = ImGui::CreateContext();\n if (context == nullptr) {\n return 2;\n }\n ImGui::SetCurrentContext(context);\n\n ImGuiIO& io = ImGui::GetIO();\n io.DisplaySize = ImVec2 { 320.0f, 240.0f };\n unsigned char* pixels = nullptr;\n int width = 0;\n int height = 0;\n io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);\n if (pixels == nullptr || width <= 0 || height <= 0) {\n ImGui::DestroyContext(context);\n return 3;\n }\n\n ImGui::NewFrame();\n bool open = true;\n ImGui::Begin(\"mcpp-index imgui smoke\", &open);\n ImGui::TextUnformatted(\"import imgui.core\");\n ImGui::End();\n ImGui::Render();\n\n std::println(\"Dear ImGui {} module package ok\", ImGui::GetVersion());\n ImGui::DestroyContext(context);\n return 0;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "imgui-window", + "text": "# imgui + glfw + the OpenGL3/GLFW backends. Linux-only (cfg). The BUILD + LINK is\n# the test (the backends compile and glfw/GL link); the actual window run needs a\n# display and is opt-in (set MCPP_RUN_WINDOW=1). Off-Linux: no-op.\n[package]\nname = \"imgui-window-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nimgui = \"1.92.8\"\nglfw = \"3.4.0.1\"\n[target.'cfg(linux)'.build]\nldflags = [\"-ldl\"]\n", + "files": [ + { + "path": "tests/examples/imgui-window/tests/imgui_glfw_backend.cpp", + "text": "// Build + link the full imgui GLFW/OpenGL3 backends (the recipe coverage). The\n// window run requires a display, so it is opt-in (MCPP_RUN_WINDOW=1); headless\n// CI compiles + links and returns 0. Off-Linux this is a no-op.\n#ifdef __linux__\n#include \n#include \n\n#define GLFW_INCLUDE_NONE\n#include \n#include \n#include \n#include \n\n#include \"imgui_impl_glfw.cpp\"\n#include \"imgui_impl_opengl3.cpp\"\n\nstatic int run_window() {\n glfwSetErrorCallback([](int code, const char* message) {\n std::fprintf(stderr, \"GLFW error %d: %s\\n\", code, message ? message : \"\");\n });\n if (!glfwInit()) return 10;\n glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);\n glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);\n glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);\n GLFWwindow* window = glfwCreateWindow(320, 180, \"mcpp compat imgui\", nullptr, nullptr);\n if (!window) { glfwTerminate(); return 11; }\n glfwMakeContextCurrent(window);\n IMGUI_CHECKVERSION();\n ImGui::CreateContext();\n ImGuiIO& io = ImGui::GetIO();\n io.IniFilename = nullptr;\n io.DisplaySize = ImVec2(320.0f, 180.0f);\n io.DeltaTime = 1.0f / 60.0f;\n if (!ImGui_ImplGlfw_InitForOpenGL(window, true)) { ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); return 12; }\n if (!ImGui_ImplOpenGL3_Init(\"#version 110\")) { ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); return 13; }\n ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame();\n ImGui::Begin(\"compat imgui\"); ImGui::Text(\"hello from mcpp\"); ImGui::End();\n ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());\n ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext();\n glfwDestroyWindow(window); glfwTerminate();\n return 0;\n}\n\nint main() {\n // The backend sources are #included above, so compiling+linking this TU is\n // the headless test. The actual window run needs a display (opt-in).\n if (std::getenv(\"MCPP_RUN_WINDOW\")) return run_window();\n return 0;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "imgui", + "text": "# Dear ImGui (compat source build): headless in-memory frame → valid draw data.\n[package]\nname = \"imgui-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nimgui = \"1.92.8\"\n", + "files": [ + { + "path": "tests/examples/imgui/tests/render.cpp", + "text": "// Headless ImGui: create context, build a font atlas, render one frame, assert\n// the draw data is valid. No window / display.\n#include \n\nint main() {\n ImGui::CreateContext();\n ImGuiIO& io = ImGui::GetIO();\n io.IniFilename = nullptr; // no imgui.ini side-effect\n io.DisplaySize = ImVec2(320.0f, 180.0f);\n io.DeltaTime = 1.0f / 60.0f;\n unsigned char* pixels = nullptr;\n int tw = 0, th = 0;\n io.Fonts->GetTexDataAsRGBA32(&pixels, &tw, &th);\n io.Fonts->SetTexID(1);\n\n ImGui::NewFrame();\n ImGui::Begin(\"compat imgui smoke\");\n ImGui::Text(\"ok\");\n ImGui::End();\n ImGui::Render();\n\n ImDrawData* dd = ImGui::GetDrawData();\n const bool ok = dd != nullptr && dd->Valid;\n ImGui::DestroyContext();\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "jwt-cpp", + "text": "# compat.jwt-cpp test project: real HMAC signing + verification through the\n# package's compat.openssl dependency — the positive path must verify, and a\n# wrong key must be rejected.\n[package]\nname = \"jwt-cpp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\njwt-cpp = \"0.7.2\"\n", + "files": [ + { + "path": "tests/examples/jwt-cpp/tests/jwt.cpp", + "text": "// Behavioral test: HS256 sign -> decode -> verify through compat.openssl.\n// The correct key and claim must pass; a wrong key must be rejected. This is\n// the end-to-end assertion that the package's OpenSSL wiring actually links\n// and computes, not just that headers exist.\n#include \n#include \n#include \n\nint main() {\n bool ok = true;\n\n const auto token = jwt::create()\n .set_payload_claim(\"sub\", jwt::claim(std::string(\"alice\")))\n // Boolean claims need the JSON value spelled out: a\n // bare `jwt::claim(true)` would need TWO user-defined\n // conversions (bool -> picojson::value -> claim).\n .set_payload_claim(\n \"admin\",\n jwt::claim(jwt::traits::kazuho_picojson::value_type(true)))\n .set_expires_at(std::chrono::system_clock::now() +\n std::chrono::seconds{3600})\n .sign(jwt::algorithm::hs256{\"secret-key\"});\n\n const auto decoded = jwt::decode(token);\n ok = ok && decoded.get_payload_claim(\"sub\").as_string() == \"alice\";\n ok = ok && decoded.get_payload_claim(\"admin\").as_boolean();\n ok = ok && decoded.get_header_claim(\"alg\").as_string() == \"HS256\";\n\n try {\n jwt::verify()\n .allow_algorithm(jwt::algorithm::hs256{\"secret-key\"})\n .with_claim(\"sub\", jwt::claim(std::string(\"alice\")))\n .verify(decoded);\n } catch (...) {\n ok = false; // correct key + claim: verification must succeed\n }\n\n bool wrong_key_rejected = false;\n try {\n jwt::verify()\n .allow_algorithm(jwt::algorithm::hs256{\"wrong-key\"})\n .with_claim(\"sub\", jwt::claim(std::string(\"alice\")))\n .verify(decoded);\n } catch (...) {\n wrong_key_rejected = true; // wrong signature must throw\n }\n\n return (ok && wrong_key_rejected) ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "libaio", + "text": "# libaio test project: drive real kernel AIO through the wrapper.\n#\n# libaio is not a portable library with a Linux backend -- it IS the Linux AIO\n# ABI -- so the descriptor has a `linux` section and nothing else, the\n# dependency is gated, and the test compiles to a no-op main() elsewhere. Same\n# shape as tests/examples/wil in the other direction.\n#\n# Nothing here is mocked: the assertions submit iocbs to the kernel and check\n# the completion events, because a package that links but mis-declares\n# `struct iocb` would pass a \"does it link\" test and corrupt every submission.\n[package]\nname = \"libaio-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibaio = \"0.3.113\"\n", + "files": [ + { + "path": "tests/examples/libaio/tests/aio.cpp", + "text": "// Behavioral test: real kernel AIO submissions, not a link check.\n//\n// Three things can go wrong with this package and none of them is a missing\n// symbol.\n//\n// 1. `struct iocb` is the KERNEL's structure, assembled by libaio.h out of\n// PADDED/PADDEDptr/PADDEDul macros chosen by endianness and word size. Get\n// that selection wrong and every field lands at the wrong offset — the\n// library still links, io_submit still returns 1, and the kernel writes\n// the wrong bytes to the wrong place. So the assertions read back what\n// actually hit the file.\n//\n// 2. The plain names `io_getevents` and `io_cancel` do not exist as ordinary\n// definitions upstream: the functions are called `io_getevents_0_4` /\n// `io_cancel_0_4` and publish the short names through\n// `.symver … @@LIBAIO_0.4`. Whether that survives into a consumer's link\n// is a linker behavior, so both are called here on purpose.\n//\n// 3. libaio does NOT use errno. Every wrapper returns `-errno` directly and\n// restores the caller's errno, which is the opposite of the convention\n// the surrounding POSIX calls follow. The error-path assertion pins that\n// down rather than assuming it.\n//\n// The static_asserts below are upstream's own src/struct_offsets.c, which this\n// package deliberately does not compile (it is a build-time check that emits no\n// library code). Restating them here keeps the check and moves it to where it\n// is actually observable.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n// ── upstream src/struct_offsets.c, verbatim in intent ────────────────────\n// The iocb.u union overlays io_iocb_common / io_iocb_vector / io_iocb_sockaddr,\n// and the kernel reads whichever the opcode implies. If the padding macros\n// picked the wrong variant these three stop agreeing.\nstatic_assert(offsetof(struct iocb, u.v.nr) == offsetof(struct iocb, u.c.nbytes),\n \"iocb.u.v.nr does not match the offset of iocb.u.c.nbytes\");\nstatic_assert(offsetof(struct iocb, u.v.offset) == offsetof(struct iocb, u.c.offset),\n \"iocb.u.v.offset does not match the offset of iocb.u.c.offset\");\nstatic_assert(offsetof(struct iocb, u.saddr.len) == offsetof(struct iocb, u.c.nbytes),\n \"iocb.u.saddr.len does not match the offset of iocb.u.c.nbytes\");\n\nnamespace {\n\nconstexpr std::size_t kBlock = 512;\n\n// Reap exactly `want` completions, or give up. 10s is far beyond what buffered\n// AIO on a temp file needs; it is a deadlock guard, not a timing assumption.\nint reap(io_context_t ctx, io_event* events, long want) {\n timespec timeout{10, 0};\n return io_getevents(ctx, want, want, events, &timeout);\n}\n\nstruct TempFile {\n char path[64] = {};\n int fd = -1;\n\n TempFile() {\n const char* dir = std::getenv(\"TMPDIR\");\n std::snprintf(path, sizeof path, \"%s/mcpp_libaio_XXXXXX\",\n (dir && *dir) ? dir : \"/tmp\");\n fd = ::mkstemp(path);\n }\n ~TempFile() {\n if (fd >= 0) ::close(fd);\n if (path[0]) ::unlink(path);\n }\n};\n\n// Set by the io_queue_run callback below.\nstruct CallbackRecord {\n int calls = 0;\n iocb* obj = nullptr;\n long res = -1;\n};\nCallbackRecord g_callback;\n\nvoid on_complete(io_context_t, iocb* cb, long res, long /*res2*/) {\n g_callback.calls += 1;\n g_callback.obj = cb;\n g_callback.res = res;\n}\n\n} // namespace\n\nint main() {\n TempFile file;\n assert(file.fd >= 0 && \"could not create the temp file the test writes through\");\n\n io_context_t ctx = nullptr;\n const int setup = io_setup(8, &ctx);\n assert(setup == 0 && \"io_setup failed; libaio returns -errno, not -1\");\n assert(ctx != nullptr);\n\n // ── io_prep_pwrite fills the iocb the kernel will read ───────────────\n // Checked before submitting: if these are wrong the submission below can\n // still \"succeed\" while writing something else entirely.\n unsigned char out[kBlock];\n for (std::size_t i = 0; i < kBlock; ++i) out[i] = static_cast(i & 0xFF);\n\n iocb write_cb;\n io_prep_pwrite(&write_cb, file.fd, out, kBlock, 0);\n assert(write_cb.aio_lio_opcode == IO_CMD_PWRITE);\n assert(write_cb.aio_fildes == file.fd);\n assert(write_cb.u.c.buf == out);\n assert(write_cb.u.c.nbytes == kBlock);\n assert(write_cb.u.c.offset == 0);\n\n // ── the write actually reaches the file ──────────────────────────────\n {\n iocb* batch[1] = {&write_cb};\n assert(io_submit(ctx, 1, batch) == 1);\n\n io_event event{};\n assert(reap(ctx, &event, 1) == 1);\n assert(event.obj == &write_cb);\n assert(static_cast(event.res) == static_cast(kBlock));\n\n unsigned char verify[kBlock] = {};\n assert(::pread(file.fd, verify, kBlock, 0) == static_cast(kBlock));\n assert(std::memcmp(verify, out, kBlock) == 0 &&\n \"the async write did not land the bytes it reported writing\");\n\n // io_cancel on a request the kernel already completed must fail, and\n // must fail libaio's way: a negative errno as the return value.\n // Calling it at all is the point — it is one of the three functions\n // that only exist under a @@LIBAIO_0.4 symbol version.\n io_event discarded{};\n assert(io_cancel(ctx, &write_cb, &discarded) < 0);\n }\n\n // ── the read path, at a non-zero offset ──────────────────────────────\n {\n unsigned char tail[kBlock];\n for (std::size_t i = 0; i < kBlock; ++i) tail[i] = static_cast(0xA5 ^ i);\n\n iocb append;\n io_prep_pwrite(&append, file.fd, tail, kBlock, kBlock);\n iocb* batch[1] = {&append};\n assert(io_submit(ctx, 1, batch) == 1);\n io_event event{};\n assert(reap(ctx, &event, 1) == 1);\n assert(static_cast(event.res) == static_cast(kBlock));\n\n unsigned char in[kBlock] = {};\n iocb read_cb;\n io_prep_pread(&read_cb, file.fd, in, kBlock, kBlock);\n assert(read_cb.aio_lio_opcode == IO_CMD_PREAD);\n assert(read_cb.u.c.offset == static_cast(kBlock));\n\n iocb* rbatch[1] = {&read_cb};\n assert(io_submit(ctx, 1, rbatch) == 1);\n assert(reap(ctx, &event, 1) == 1);\n assert(static_cast(event.res) == static_cast(kBlock));\n assert(std::memcmp(in, tail, kBlock) == 0 &&\n \"the async read at offset 512 returned the wrong block\");\n }\n\n // ── a batch of two, told apart by iocb.data ──────────────────────────\n // `data` is the first member of the padded struct, so a wrong PADDEDptr\n // choice shows up here as a cookie that does not come back.\n {\n unsigned char a[kBlock], b[kBlock];\n std::memset(a, 0x11, sizeof a);\n std::memset(b, 0x22, sizeof b);\n\n iocb first, second;\n io_prep_pwrite(&first, file.fd, a, kBlock, 2 * kBlock);\n io_prep_pwrite(&second, file.fd, b, kBlock, 3 * kBlock);\n first.data = &first;\n second.data = &second;\n\n iocb* batch[2] = {&first, &second};\n assert(io_submit(ctx, 2, batch) == 2);\n\n io_event events[2]{};\n assert(reap(ctx, events, 2) == 2);\n\n bool saw_first = false, saw_second = false;\n for (const io_event& e : events) {\n assert(static_cast(e.res) == static_cast(kBlock));\n if (e.data == &first) saw_first = true;\n if (e.data == &second) saw_second = true;\n }\n assert(saw_first && saw_second && \"iocb.data cookies did not round-trip\");\n\n unsigned char verify[kBlock] = {};\n assert(::pread(file.fd, verify, kBlock, 3 * kBlock) == static_cast(kBlock));\n assert(verify[0] == 0x22 && verify[kBlock - 1] == 0x22);\n }\n\n // ── the error contract: -errno returned, caller's errno untouched ────\n {\n unsigned char scratch[kBlock] = {};\n iocb bad;\n io_prep_pwrite(&bad, -1, scratch, kBlock, 0);\n iocb* batch[1] = {&bad};\n\n errno = 0;\n const int rc = io_submit(ctx, 1, batch);\n assert(rc < 0 && \"io_submit on a closed fd must fail\");\n assert(rc == -EBADF && \"libaio reports -errno as the return value\");\n assert(errno == 0 && \"libaio must not clobber the caller's errno\");\n }\n\n assert(io_destroy(ctx) == 0);\n\n // ── the io_queue_* convenience layer and its callback ────────────────\n // A separate context, because io_queue_init owns setup/teardown. This is\n // the only path that reads iocb.data as a FUNCTION POINTER, so it is the\n // one place a callback that never fires is a real failure rather than a\n // slow completion.\n {\n io_context_t qctx = nullptr;\n assert(io_queue_init(8, &qctx) == 0);\n\n unsigned char payload[kBlock];\n std::memset(payload, 0x5A, sizeof payload);\n\n iocb cb;\n io_prep_pwrite(&cb, file.fd, payload, kBlock, 4 * kBlock);\n io_set_callback(&cb, on_complete);\n assert(cb.data == reinterpret_cast(&on_complete));\n\n iocb* batch[1] = {&cb};\n assert(io_submit(qctx, 1, batch) == 1);\n\n // io_queue_run polls with a zero timeout and returns 0 when the ring is\n // empty, so it is spun until the completion shows up.\n for (int i = 0; i < 1000 && g_callback.calls == 0; ++i) {\n assert(io_queue_run(qctx) >= 0);\n if (g_callback.calls == 0) {\n timespec nap{0, 1'000'000}; // 1ms; 1000 tries = 1s ceiling\n ::nanosleep(&nap, nullptr);\n }\n }\n\n assert(g_callback.calls == 1 && \"io_queue_run never dispatched the callback\");\n assert(g_callback.obj == &cb);\n assert(g_callback.res == static_cast(kBlock));\n\n unsigned char verify[kBlock] = {};\n assert(::pread(file.fd, verify, kBlock, 4 * kBlock) == static_cast(kBlock));\n assert(verify[0] == 0x5A && verify[kBlock - 1] == 0x5A);\n\n assert(io_queue_release(qctx) == 0);\n }\n\n return 0;\n}\n\n#else\n\nint main() { return 0; } // libaio is the Linux AIO ABI; nothing to assert here.\n\n#endif\n" + } + ] + }, + { + "project": "libassert", + "text": "# compat.libassert test project. Declares ONLY libassert: cpptrace has to\n# arrive transitively, which is the edge this member exists to prove.\n# `compat` redirect inherited from the workspace root.\n[package]\nname = \"libassert-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nlibassert = \"2.2.1\"\n", + "files": [ + { + "path": "tests/examples/libassert/tests/assertions.cpp", + "text": "// Behavioral test — compat.libassert decomposes an expression and reports the\n// values in it, which is the whole reason to use it over .\n//\n// A passing assertion proves almost nothing, so the test drives the FAILING\n// path: it installs a failure handler, provokes a failed assertion, and reads\n// what libassert produced. `LIBASSERT_ASSERT` carries `LIBASSERT_NOP_ACTION`,\n// so a handler that returns lets execution continue — which is what makes this\n// testable in-process rather than through an abort.\n//\n// It is also the proof that the transitive dependency arrives: the failure\n// object hands back a `cpptrace::stacktrace`, and this project declares only\n// libassert.\n//\n// No `import std;` — libassert's headers are textual over the standard library.\n#include \n#include \n// libassert's own header only FORWARD-declares cpptrace::stacktrace\n// (cpptrace/forward.hpp), so calling .frames on it needs the complete type.\n// Including cpptrace's public header here works only because compat.cpptrace's\n// include directory reaches this project transitively through libassert — this\n// project declares no cpptrace dependency of its own. The include is therefore\n// part of the assertion, not incidental.\n#include \n\n#include \n#include \n\n#if LIBASSERT_VERSION != LIBASSERT_TO_VERSION(2, 2, 1)\n# error \"libassert/version.hpp does not describe 2.2.1 — the generated header drifted from the tarball\"\n#endif\n\n// Both packages build objects rather than shared libraries, so the\n// declarations this TU sees must be plain. Each delivers that with a shim in\n// front of the one header that reads its macro — no descriptor key carries a\n// define into a consumer's TUs. On Linux the difference is only a visibility\n// attribute, so a regression is invisible; on the MSVC ABI it is `dllimport`\n// against a locally defined symbol, which is exactly how this member first\n// failed on the windows CI leg.\n#if !defined(LIBASSERT_STATIC_DEFINE)\n# error \"LIBASSERT_STATIC_DEFINE is not set — the compat.libassert platform.hpp shim was not reached\"\n#endif\n#if !defined(CPPTRACE_STATIC_DEFINE)\n# error \"CPPTRACE_STATIC_DEFINE is not set — compat.cpptrace's basic.hpp shim did not reach a transitive consumer\"\n#endif\n\nstatic std::string captured;\nstatic std::size_t trace_frames = 0;\nstatic int failures = 0;\n\nstatic void record(const libassert::assertion_info& info) {\n ++failures;\n captured = info.to_string(0, libassert::color_scheme::blank);\n trace_frames = info.get_stacktrace().frames.size();\n}\n\nint main() {\n libassert::set_failure_handler(record);\n\n const int limit = 3;\n const int index = 7;\n\n // A true assertion must not reach the handler.\n LIBASSERT_ASSERT(limit < index);\n if (failures != 0) {\n std::printf(\"a true assertion fired the handler\\n\");\n return 1;\n }\n\n // A false one must, exactly once.\n LIBASSERT_ASSERT(index < limit, \"index out of range\", index, limit);\n if (failures != 1) {\n std::printf(\"failure handler fired %d times, expected 1\\n\", failures);\n return 2;\n }\n\n // The report has to carry the EXPRESSION TEXT — that is the part a plain\n // `assert` also manages — and then the two things it does not: the message\n // and the decomposed operand values.\n if (captured.find(\"index < limit\") == std::string::npos) {\n std::printf(\"report does not carry the expression text:\\n%s\\n\", captured.c_str());\n return 3;\n }\n if (captured.find(\"index out of range\") == std::string::npos) {\n std::printf(\"report does not carry the message:\\n%s\\n\", captured.c_str());\n return 4;\n }\n if (captured.find(\"7\") == std::string::npos || captured.find(\"3\") == std::string::npos) {\n std::printf(\"report does not carry the decomposed values:\\n%s\\n\", captured.c_str());\n return 5;\n }\n\n // cpptrace, reached transitively. Frame COUNT rather than symbol names:\n // compat.cpptrace resolves symbols with dladdr and documents that it\n // carries no file:line, so asserting on names would be testing the\n // backend choice rather than the dependency edge.\n if (trace_frames == 0) {\n std::printf(\"assertion carried no stack trace — cpptrace did not arrive\\n\");\n return 6;\n }\n\n std::printf(\"compat.libassert: ok (expression, message and values decomposed; %zu trace frames)\\n\",\n trace_frames);\n return 0;\n}\n" + } + ] + }, + { + "project": "libcoro", + "text": "# compat.libcoro test project — the CORE (non-networking) half, which is all\n# the pinned tarball can build: upstream's c-ares lives in a git submodule that\n# a GitHub archive does not carry. `compat` redirect inherited from the root.\n[package]\nname = \"libcoro-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nlibcoro = \"0.16.0\"\n", + "files": [ + { + "path": "tests/examples/libcoro/tests/coroutines.cpp", + "text": "// Behavioral test — compat.libcoro actually schedules coroutines.\n//\n// Every assertion here needs a resumed coroutine to be true, so a package that\n// compiled the headers but linked none of the eight core TUs fails: the\n// thread_pool, event, mutex and sync_wait entry points all live in those.\n//\n// Networking is deliberately untouched — see the descriptor. `coro/coro.hpp`\n// is NOT included for the same reason; it opens the net/ headers behind\n// LIBCORO_FEATURE_NETWORKING, which this build does not define.\n//\n// No `import std;`: libcoro's headers are textual over the standard library.\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n\nint main() {\n // A task returning a value, driven to completion synchronously.\n auto add = [](int a, int b) -> coro::task { co_return a + b; };\n if (coro::sync_wait(add(2, 3)) != 5) {\n std::printf(\"sync_wait(task) did not produce the value\\n\");\n return 1;\n }\n\n // A generator is lazy: nothing runs until the range is iterated.\n auto squares = [](int n) -> coro::generator {\n for (int i = 1; i <= n; ++i) co_yield i * i;\n };\n int sum = 0;\n for (int v : squares(4)) sum += v;\n if (sum != 1 + 4 + 9 + 16) {\n std::printf(\"generator produced %d, expected 30\\n\", sum);\n return 2;\n }\n\n // The thread pool really moves work off this thread, and coro::mutex\n // serialises it. Both are compiled TUs, not header inlines — this is the\n // link-time proof for src/thread_pool.cpp and src/mutex.cpp.\n // 0.16.0 makes the constructor private: a pool is created through the\n // make_unique factory, which lives in src/thread_pool.cpp — so this line\n // is also the link-time proof that the TU is compiled in.\n auto pool_ptr = coro::thread_pool::make_unique(coro::thread_pool::options{.thread_count = 4});\n coro::thread_pool& pool = *pool_ptr;\n coro::mutex mutex;\n int guarded = 0;\n\n std::vector> tasks;\n tasks.reserve(200);\n for (int i = 0; i < 200; ++i) {\n tasks.emplace_back([](coro::thread_pool& p, coro::mutex& m, int& counter) -> coro::task {\n co_await p.schedule();\n auto lock = co_await m.scoped_lock();\n ++counter;\n co_return;\n }(pool, mutex, guarded));\n }\n coro::sync_wait(coro::when_all(std::move(tasks)));\n\n if (guarded != 200) {\n std::printf(\"200 coroutines incremented the counter to %d — the mutex did not serialise them\\n\", guarded);\n return 3;\n }\n\n // coro::event: a coroutine suspends until another thread sets it.\n coro::event started;\n std::atomic observed{false};\n auto waiter = [&]() -> coro::task {\n co_await started;\n observed = true;\n co_return;\n };\n auto setter = [&]() -> coro::task {\n co_await pool.schedule();\n started.set();\n co_return;\n };\n coro::sync_wait(coro::when_all(waiter(), setter()));\n if (!observed) {\n std::printf(\"coro::event never released the waiter\\n\");\n return 4;\n }\n\n std::printf(\"compat.libcoro: ok (task, generator, 200 pooled tasks under a coro::mutex, event)\\n\");\n return 0;\n}\n" + } + ] + }, + { + "project": "libdisplay-info", + "text": "# libdisplay-info test member — EDID parsing, the gate on wlroots.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"libdisplay-info-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.freedesktop]\nlibdisplay-info = \"0.2.0\"\n", + "files": [ + { + "path": "tests/examples/libdisplay-info/tests/libdisplay-info.cpp", + "text": "// freedesktop.libdisplay-info — parse a real monitor's EDID.\n//\n// EDID parsing needs no device, no compositor and no permissions: it is a\n// function from 128 bytes to a struct. So unlike most of this stack, the whole\n// library is honestly testable on a CI runner.\n//\n// THE BLOB IS INLINE, not read from the package's own test data. A test member\n// resolves its dependency into the mcpp store, and reaching into that tree by\n// relative path would tie this file to a layout that is mcpp's business. 256\n// bytes of a real Acer P1276's EDID, taken from upstream's\n// `test/data/acer-p1276.edid`, is cheaper than that coupling.\n//\n// THE EXPECTED VALUES ARE UPSTREAM'S, from the `.ref` beside that blob:\n//\n// EDID Structure Version & Revision: 1.3\n// Manufacturer: ACR\n// Model: 5654 (0x1616)\n//\n// WHAT IS ACTUALLY UNDER TEST is the generated PNP table. \"ACR\" is the raw\n// vendor id in the blob; turning it into \"Acer Technologies\" is what\n// `pnp-id-table.c` does — the file this package is a fork in order to\n// pre-generate. A build that used the host's pnp.ids instead of the pinned one\n// would still parse the EDID and could still answer differently here.\n\n#ifdef __linux__\n\n// NO extern \"C\" WRAPPER, and that is the point of the module.\n//\n// Not one of libdisplay-info's seven public headers contains an `extern \"C\"`\n// block — zero occurrences, measured — so a C++ translation unit that\n// #includes them mangles every declaration and fails to link with\n// `undefined reference to di_info_get_make(di_info const*)`, naming a symbol\n// that is right there in the library. This file did exactly that before the\n// module existed.\n//\n// `import displayinfo;` does the wrapping once, inside the module\n// purview. The module is GENERATED by the package's build.mcpp from the public\n// headers — 206 names — so it cannot fall behind them.\nimport displayinfo;\n\n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// An Acer P1276, from upstream/test/data/acer-p1276.edid.\nconst unsigned char ACER_P1276[] = {\n \n0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x72, 0x16, 0x16,\n 0x3e, 0x13, 0x00, 0x00, 0x17, 0x17, 0x01, 0x03, 0x0e, 0x00, 0x00, 0x00,\n 0x2f, 0x41, 0xf0, 0x9e, 0x5c, 0x58, 0x89, 0x25, 0x16, 0x53, 0x5f, 0x3f,\n 0xcf, 0x80, 0x31, 0x7c, 0x45, 0x7c, 0x61, 0x7c, 0x81, 0x80, 0xa9, 0x40,\n 0x95, 0x00, 0x81, 0xc0, 0x81, 0x00, 0x64, 0x19, 0x00, 0x40, 0x41, 0x00,\n 0x26, 0x30, 0x18, 0x88, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,\n 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, 0x78, 0x1e, 0x64, 0x11, 0x00, 0x0a,\n 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x50,\n 0x31, 0x32, 0x37, 0x36, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n 0x00, 0x00, 0x00, 0xff, 0x00, 0x4a, 0x47, 0x47, 0x31, 0x31, 0x30, 0x30,\n 0x31, 0x35, 0x39, 0x30, 0x30, 0x0a, 0x01, 0xa2, 0x02, 0x03, 0x04, 0x00,\n 0x66, 0x21, 0x56, 0xaa, 0x51, 0x00, 0x1e, 0x30, 0x46, 0x8f, 0x33, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0xab\n};\n\n} // namespace\n\nint main()\n{\n std::printf(\" %zu bytes of EDID\\n\", sizeof ACER_P1276);\n\n di_info *info = di_info_parse_edid(ACER_P1276, sizeof ACER_P1276);\n check(info != nullptr, \"di_info_parse_edid on a real monitor's EDID\");\n if (info == nullptr) {\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── The generated PNP table, which is why this package is a fork ─────\n char *make = di_info_get_make(info);\n std::printf(\" make = %s\\n\", make ? make : \"(null)\");\n check(make != nullptr, \"di_info_get_make returned a manufacturer\");\n check(make != nullptr && std::strstr(make, \"Acer\") != nullptr,\n \"…and the pinned PNP table resolved ACR to Acer\");\n\n char *model = di_info_get_model(info);\n std::printf(\" model = %s\\n\", model ? model : \"(null)\");\n check(model != nullptr, \"di_info_get_model returned a model\");\n\n // ── The parse agrees with upstream's own reference output ────────────\n const di_edid *edid = di_info_get_edid(info);\n check(edid != nullptr, \"di_info_get_edid exposes the parsed block\");\n if (edid != nullptr) {\n const int major = di_edid_get_version(edid);\n const int minor = di_edid_get_revision(edid);\n std::printf(\" EDID version %d.%d\\n\", major, minor);\n check(major == 1 && minor == 3, \"…and it is 1.3, as upstream's .ref says\");\n\n const di_edid_vendor_product *vp = di_edid_get_vendor_product(edid);\n check(vp != nullptr, \"di_edid_get_vendor_product\");\n if (vp != nullptr) {\n std::printf(\" pnp id %.3s, product 0x%04x\\n\",\n vp->manufacturer, vp->product);\n check(std::strncmp(vp->manufacturer, \"ACR\", 3) == 0,\n \"…and the raw PNP id is ACR\");\n check(vp->product == 0x1616,\n \"…and the product code is 5654, as the .ref says\");\n }\n }\n\n std::free(make);\n std::free(model);\n di_info_destroy(info);\n // ⭐ AN ENUMERATOR, WHICH THIS EXAMPLE NEVER ASKED FOR BEFORE.\n //\n // The module exported 206 names and NOT ONE of the 295 `DI_*` enumerators.\n // Nothing noticed, because no test named one — and naming one on GCC alone\n // would not have noticed either: exporting the enum makes its enumerators\n // visible there and clang rejects the same file. So this is the check, and\n // it is a compile error if the export list collapses again.\n const enum di_edid_cvt_aspect_ratio ar = DI_EDID_CVT_ASPECT_RATIO_16_9;\n check(ar != DI_EDID_CVT_ASPECT_RATIO_4_3,\n \"an enumerator comes through the module by name\");\n\n check(true, \"di_info_destroy\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "libdrm", + "text": "# libdrm test project.\n#\n# DRM/KMS is a Linux kernel interface, so like tests/examples/libgbm the\n# descriptor has a `linux` section only, the dependency is gated, and the test\n# compiles to a no-op main() elsewhere.\n#\n# The interesting failure here is the include layout, not the link. libdrm\n# installs its public headers at the include root and the uapi headers they\n# include in a `libdrm/` subdirectory, and `xf86drm.h` line 40 is a bare\n# `#include ` — so a package exposing one root builds nothing at all.\n# This member's first assertion is simply that the translation unit compiles.\n[package]\nname = \"libdrm-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibdrm = \"2.4.134\"\n", + "files": [ + { + "path": "tests/examples/libdrm/tests/drm.cpp", + "text": "// compat.libdrm — behavioral test, runnable with no GPU and no DRM node.\n//\n// Three things can be wrong with this package and none is a missing symbol.\n//\n// 1. THE INCLUDE LAYOUT. libdrm splits its headers across two roots — the\n// public ones (`xf86drm.h`) at the include root, the uapi ones (`drm.h`,\n// `drm_mode.h`, `drm_fourcc.h`) under `libdrm/` — and `xf86drm.h` itself\n// does `#include `. Exposing one root fails at line 40 of the\n// header, before any of this code exists. So the fact that this file\n// COMPILES is the first assertion, and it is not a trivial one.\n//\n// 2. THE LIBRARY COULD BE ABSENT while the headers are present. The dlsym\n// checks below pin that: a header-only package would sail past\n// compilation and fail here.\n//\n// 2b. THE WRONG libdrm COULD BE LOADED. This package builds libdrm from\n// source, and the ecosystem's Mesa payload carries its own\n// `libdrm.so.2` under `xim-x-libdrm/`. Both can be reachable at once, and\n// because they share a soname only ONE is ever mapped — silently. The\n// dladdr check pins which one won: it must be this package's build, or\n// the source build is decorative and the consumer is really running the\n// payload's copy.\n//\n// 3. THE UAPI CONSTANTS COULD DISAGREE with the library's. drm_fourcc.h is\n// the kernel's, and DRM_FORMAT_XRGB8888 must be the same fourcc GBM calls\n// GBM_FORMAT_XRGB8888 — the two APIs exchange exactly these values across\n// the gbm_bo -> drmModeAddFB2 boundary, so a mismatch would surface as a\n// display that shows the wrong colours rather than as an error.\n//\n// Opening a real DRM device is opt-in (MCPP_RUN_DRM_DEVICE=1): CI runners have\n// no /dev/dri at all.\n\n#ifdef __linux__\n\n#include // public API; pulls from the second root\n#include // the KMS family\n#include // uapi, from the libdrm/ root\n\n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. Both include roots resolved ───────────────────────────────────\n // Reaching this line at all means found the it\n // includes. Assert a constant from each root so the check is explicit\n // rather than implied by compilation succeeding.\n check(DRM_MODE_TYPE_PREFERRED != 0,\n \"xf86drmMode.h (public root) provides DRM_MODE_TYPE_PREFERRED\");\n check(DRM_FORMAT_XRGB8888 != 0,\n \"drm_fourcc.h (libdrm/ root) provides DRM_FORMAT_XRGB8888\");\n\n // ── 2. The fourcc agrees with GBM's ──────────────────────────────────\n // gbm_bo_get_format() returns a value handed straight to drmModeAddFB2.\n // 'X','R','2','4' little-endian — the same number compat.libgbm asserts as\n // \"XR24\". If these ever diverge, a KMS consumer shows wrong colours and\n // nothing reports an error.\n check(DRM_FORMAT_XRGB8888 == ((std::uint32_t)'X' | ((std::uint32_t)'R' << 8)\n | ((std::uint32_t)'2' << 16)\n | ((std::uint32_t)'4' << 24)),\n \"DRM_FORMAT_XRGB8888 is the 'XR24' fourcc GBM also uses\");\n\n // ── 3. The library is really linked ──────────────────────────────────\n for (const char *sym : {\"drmGetVersion\", \"drmFreeVersion\", \"drmModeGetResources\",\n \"drmModeAddFB2\", \"drmModeSetCrtc\", \"drmPrimeHandleToFD\"}) {\n check(::dlsym(RTLD_DEFAULT, sym) != nullptr,\n (std::string(\"libdrm exports \") + sym).c_str());\n }\n\n // ── 3b. …and it is THIS package's build, not the payload's ───────────\n // Same soname, so only one libdrm.so.2 is mapped and nothing warns about\n // the other. dladdr reports the object a symbol actually came from; if the\n // path names the ecosystem payload, this package built a library that no\n // one loads.\n {\n Dl_info info{};\n const bool located =\n ::dladdr(reinterpret_cast(&drmGetVersion), &info) != 0\n && info.dli_fname != nullptr;\n check(located, \"dladdr locates the loaded libdrm\");\n if (located) {\n const std::string from = info.dli_fname;\n std::printf(\" loaded from: %s\\n\", from.c_str());\n check(from.find(\"xim-x-libdrm\") == std::string::npos,\n \"the loaded libdrm is not the ecosystem payload's copy\");\n }\n }\n\n // ── 4. An invalid fd is rejected, not crashed on ─────────────────────\n check(drmGetVersion(-1) == nullptr, \"drmGetVersion(-1) == nullptr\");\n\n // ── 5. A real device, opt-in ─────────────────────────────────────────\n if (std::getenv(\"MCPP_RUN_DRM_DEVICE\") != nullptr) {\n const int fd = ::open(\"/dev/dri/card0\", O_RDWR);\n if (fd < 0) {\n std::printf(\" MCPP_RUN_DRM_DEVICE set but /dev/dri/card0 did not \"\n \"open; skipping\\n\");\n } else {\n drmVersionPtr v = drmGetVersion(fd);\n check(v != nullptr, \"drmGetVersion on a real DRM node\");\n if (v != nullptr) {\n std::printf(\" driver: %s\\n\", v->name ? v->name : \"(null)\");\n drmFreeVersion(v);\n }\n ::close(fd);\n }\n } else {\n std::printf(\" (device access is opt-in: set MCPP_RUN_DRM_DEVICE=1 on \"\n \"a machine with /dev/dri)\\n\");\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main()\n{\n return 0;\n}\n\n#endif\n" + } + ] + }, + { + "project": "libevdev", + "text": "# libevdev test project.\n#\n# THIS MEMBER DECLARES ITS OWN [indices]: an index is looked up BY NAMESPACE,\n# and the workspace root maps `compat`.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"libevdev-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.freedesktop]\nlibevdev = \"1.13.7\"\n", + "files": [ + { + "path": "tests/examples/libevdev/tests/libevdev.cpp", + "text": "// libevdev, exercised without an input device.\n//\n// The package's whole reason to be a fork is `event-names.h` — 1,692 lines of\n// generated tables mapping every kernel input constant to its string. So that\n// is what the test asks about: a package that linked but whose tables were\n// generated from the WRONG headers (the host's rather than the bundled ones,\n// which produces a measurably different file) answers these differently.\n//\n// No device is opened. libevdev's name lookups are pure functions of the\n// tables, which is exactly why they are testable anywhere.\n\n#ifdef __linux__\n\n#include \n#include \n\n#include \n#include \n#include \n\nnamespace {\nint failures = 0;\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n} // namespace\n\nint main()\n{\n // ── 1. The generated tables are linked in ────────────────────────────\n // Each of these reads a different table in event-names.h.\n struct { unsigned int type; unsigned int code; const char *want; } names[] = {\n {EV_KEY, KEY_A, \"KEY_A\"},\n {EV_KEY, KEY_LEFTCTRL, \"KEY_LEFTCTRL\"},\n {EV_ABS, ABS_MT_POSITION_X, \"ABS_MT_POSITION_X\"},\n {EV_REL, REL_WHEEL, \"REL_WHEEL\"},\n {EV_SW, SW_LID, \"SW_LID\"},\n };\n for (auto &n : names) {\n const char *got = libevdev_event_code_get_name(n.type, n.code);\n std::printf(\" %-20s -> %s\\n\", n.want, got ? got : \"(null)\");\n check(got != nullptr && std::strcmp(got, n.want) == 0,\n (std::string(\"libevdev names \") + n.want).c_str());\n }\n\n // ── 2. …and the type table too ───────────────────────────────────────\n const char *t = libevdev_event_type_get_name(EV_ABS);\n check(t != nullptr && std::strcmp(t, \"EV_ABS\") == 0,\n \"libevdev names the event TYPE table as well\");\n\n // ── 3. The reverse direction, which uses a different table ───────────\n // Name -> code is what libinput's quirks parser does with a config file.\n check(libevdev_event_code_from_name(EV_KEY, \"KEY_ESC\") == KEY_ESC,\n \"libevdev resolves a name back to its code\");\n check(libevdev_event_code_from_name(EV_KEY, \"KEY_NO_SUCH_THING\") == -1,\n \"…and a name that does not exist resolves to -1\");\n\n // ── 4. A device object with no fd ────────────────────────────────────\n // libevdev_new is the allocation path libinput takes before it has an fd;\n // it must work with none.\n libevdev *dev = libevdev_new();\n check(dev != nullptr, \"libevdev_new with no file descriptor\");\n if (dev != nullptr) {\n libevdev_set_name(dev, \"mcpp test device\");\n check(std::strcmp(libevdev_get_name(dev), \"mcpp test device\") == 0,\n \"…and its name round-trips\");\n libevdev_free(dev);\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "libffi", + "text": "# compat.libffi test project.\n#\n# libffi is built from source here, and the ecosystem also carries\n# `xim:libffi` at 3.4.4 as a transitive of `xim:mesa`. They share the soname\n# `libffi.so.8`, so only one is ever mapped and nothing warns about the other —\n# the dladdr assertion in the test pins which one won.\n[package]\nname = \"libffi-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibffi = \"3.4.8\"\n", + "files": [ + { + "path": "tests/examples/libffi/tests/ffi.cpp", + "text": "// compat.libffi — behavioral test. It does not check that symbols exist; it\n// builds a call frame at runtime and invokes through it, which is the only\n// thing that proves the per-ABI assembly was assembled and linked correctly.\n// A wrong or missing unix64.S links fine and segfaults here.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n\nint add3(int a, int b, int c) { return a + b + c; }\n\ndouble scale(double v, float f) { return v * static_cast(f); }\n\n} // namespace\n\nint main()\n{\n // ── 1. A real integer call through ffi_call ──────────────────────────\n {\n ffi_cif cif{};\n ffi_type *args[3] = { &ffi_type_sint, &ffi_type_sint, &ffi_type_sint };\n check(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 3, &ffi_type_sint, args) == FFI_OK,\n \"ffi_prep_cif on (int,int,int) -> int\");\n\n int a = 17, b = 25, c = -2, result = 0;\n void *values[3] = { &a, &b, &c };\n ffi_call(&cif, FFI_FN(add3), &result, values);\n check(result == 40, \"ffi_call dispatched add3(17,25,-2) == 40\");\n }\n\n // ── 2. Mixed float/double, which uses different argument registers ───\n // SSE arguments travel a separate path in unix64.S from integers, so an\n // integer-only test would miss half of the assembly.\n {\n ffi_cif cif{};\n ffi_type *args[2] = { &ffi_type_double, &ffi_type_float };\n check(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_double, args) == FFI_OK,\n \"ffi_prep_cif on (double,float) -> double\");\n\n double v = 1.5, result = 0.0;\n float f = 4.0f;\n void *values[2] = { &v, &f };\n ffi_call(&cif, FFI_FN(scale), &result, values);\n check(result == 6.0, \"ffi_call dispatched scale(1.5, 4.0f) == 6.0\");\n }\n\n // ── 3. It is THIS build that ran, not the ecosystem payload's ────────\n // Same soname as `xim:libffi`, so one mapping wins silently. If the path\n // names the payload, this package compiled a library nobody loads.\n {\n Dl_info info{};\n const bool located =\n ::dladdr(reinterpret_cast(&ffi_prep_cif), &info) != 0\n && info.dli_fname != nullptr;\n check(located, \"dladdr locates the loaded libffi\");\n if (located) {\n const std::string from = info.dli_fname;\n std::printf(\" loaded from: %s\\n\", from.c_str());\n check(from.find(\"xim-x-libffi\") == std::string::npos,\n \"the loaded libffi is not the ecosystem payload's copy\");\n }\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "libgbm", + "text": "# libgbm test project.\n#\n# GBM is the DRM buffer-management API, so like tests/examples/libaio the\n# descriptor has a `linux` section and nothing else, the dependency is gated,\n# and the test compiles to a no-op main() elsewhere.\n#\n# What this member proves is not \"does -lgbm resolve\". compat.libgbm is a thin\n# BINDING onto the ecosystem's Mesa — it compiles no upstream source and sets\n# no environment — so the interesting failures are all in the seams:\n#\n# * the header could come from a different Mesa than the library (both are\n# taken from the same subos view precisely so they cannot);\n# * the backend directory could be unreachable, which is the default state of\n# an mcpp sandbox — Mesa compiles `/usr/lib/gbm` in as its search path and\n# that does not exist there;\n# * and the repair for that lives OUTSIDE this repository: `xim:mesa`\n# declares GBM_BACKENDS_PATH through the graphics discovery layer, and mcpp\n# carries subos declarations into the processes it launches.\n#\n# That last point is why this member matters more than a link check: it is the\n# only place in this repo that notices if either of those two breaks.\n#\n# Two binaries, deliberately:\n#\n# tests/gbm.cpp the full surface — format canonicalization through\n# the real library, symbol presence, invalid-device\n# handling, and the backend-path assertions.\n# tests/stock_usage.cpp the minimal consumer: one header, one library,\n# nothing else. Smallest thing that still detects all\n# three regressions above, so a failure is unambiguous.\n#\n# Everything asserted here runs on a CI runner with no /dev/dri. Real device\n# creation is opt-in behind MCPP_RUN_GBM_DEVICE=1.\n[package]\nname = \"libgbm-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibgbm = \"25.0.7\"\n", + "files": [ + { + "path": "tests/examples/libgbm/tests/gbm.cpp", + "text": "// compat.libgbm — behavioral test, runnable on a machine with no GPU.\n//\n// The package is a BINDING onto the ecosystem's Mesa rather than a source\n// build, so the ways it can be wrong are not missing symbols:\n//\n// 1. The header could come from a different Mesa than the library. Both are\n// taken from the subos view for exactly this reason, and the assertions\n// below call through the header into the library to keep that honest.\n//\n// 2. The library could be present and the BACKEND unreachable. libgbm is a\n// loader: gbm_create_device() dlopens `/_gbm.so`, where\n// is GBM_BACKENDS_PATH or the `/usr/lib/gbm` compiled into Mesa —\n// correct on a distro, wrong the moment the payload is relocated:\n//\n// MESA-LOADER: failed to open dri: /usr/lib/gbm/dri_gbm.so: cannot\n// open shared object file (search paths /usr/lib/gbm, suffix _gbm)\n//\n// Setting that variable is the ENVIRONMENT's job, not this package's, and\n// `xim:mesa` now does it through the graphics discovery layer\n// (openxlings/xim-pkgindex#713). So the check below is a check on the\n// ECOSYSTEM: if the declaration is ever dropped from that table, or the\n// backends stop being placed into the subos, this goes red here.\n//\n// WHY THE LEGACY ENUM IS ASSERTED. gbm_format_get_name(GBM_FORMAT_XRGB8888) is\n// a weak test on its own — the answer is four bytes of the fourcc and a\n// header-only reimplementation would produce it. GBM_BO_FORMAT_XRGB8888 is the\n// value 0, and only the LIBRARY's format_canonicalize() turns it into \"XR24\".\n// So that case is what proves the calls land in Mesa's libgbm.\n//\n// Creating a real device is opt-in (MCPP_RUN_GBM_DEVICE=1) and needs\n// /dev/dri: CI runners have no DRM device, and on a host whose xim-x-mesa is\n// built against a newer glibc than xim-x-glibc the backend is found and then\n// fails to dlopen — an ecosystem-stack skew this package does not cause and\n// must not assert its way around.\n\n#ifdef __linux__\n\n// Stock libgbm, and nothing else. This package ships no header of its own.\n#include \n\n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// gbm_format_get_name writes into desc->name and returns it.\nstd::string format_name(std::uint32_t format)\n{\n gbm_format_name_desc desc {};\n gbm_format_get_name(format, &desc);\n return std::string(desc.name);\n}\n\nbool dir_has_backend(const char *dir)\n{\n DIR *d = ::opendir(dir);\n if (d == nullptr) {\n return false;\n }\n\n // Mesa's BACKEND_LIB_SUFFIX is \"_gbm\", so a backend is \"_gbm.so\"\n // -- 7 trailing characters, and the name must be longer than the suffix\n // alone for there to be a driver name in front of it.\n static const char suffix[] = \"_gbm.so\";\n const std::size_t suffix_len = sizeof(suffix) - 1;\n\n bool found = false;\n while (dirent *e = ::readdir(d)) {\n const std::size_t n = std::strlen(e->d_name);\n if (n > suffix_len &&\n std::strcmp(e->d_name + (n - suffix_len), suffix) == 0) {\n found = true;\n break;\n }\n }\n ::closedir(d);\n return found;\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. The calls reach Mesa's libgbm ─────────────────────────────────\n // Pure functions: no device, no GPU, no DRM node.\n check(format_name(GBM_FORMAT_XRGB8888) == \"XR24\",\n \"gbm_format_get_name(GBM_FORMAT_XRGB8888) == \\\"XR24\\\"\");\n check(format_name(GBM_FORMAT_ARGB8888) == \"AR24\",\n \"gbm_format_get_name(GBM_FORMAT_ARGB8888) == \\\"AR24\\\"\");\n check(format_name(GBM_FORMAT_NV12) == \"NV12\",\n \"gbm_format_get_name(GBM_FORMAT_NV12) == \\\"NV12\\\"\");\n check(format_name(GBM_FORMAT_ABGR2101010) == \"AB30\",\n \"gbm_format_get_name(GBM_FORMAT_ABGR2101010) == \\\"AB30\\\"\");\n\n // The one that cannot be answered by the header: the legacy enumerator is\n // 0, and format_canonicalize() inside the library maps it to the fourcc.\n check(format_name(static_cast(GBM_BO_FORMAT_XRGB8888)) == \"XR24\",\n \"library canonicalizes GBM_BO_FORMAT_XRGB8888 (== 0) to \\\"XR24\\\"\");\n check(format_name(static_cast(GBM_BO_FORMAT_ARGB8888)) == \"AR24\",\n \"library canonicalizes GBM_BO_FORMAT_ARGB8888 (== 1) to \\\"AR24\\\"\");\n\n // ── 2. Header and library are the same Mesa ──────────────────────────\n // Every function the header declares and this test names must actually be\n // in the loaded object. A header from a newer Mesa than the library shows\n // up here rather than as a link error, because the farm resolves -lgbm to\n // whatever the subos view holds.\n for (const char *sym : {\"gbm_format_get_name\", \"gbm_create_device\",\n \"gbm_device_destroy\", \"gbm_device_get_backend_name\",\n \"gbm_bo_create\", \"gbm_surface_create\"}) {\n check(::dlsym(RTLD_DEFAULT, sym) != nullptr,\n (std::string(\"libgbm exports \") + sym).c_str());\n }\n\n // ── 3. An invalid device is rejected, not crashed on ─────────────────\n check(gbm_create_device(-1) == nullptr,\n \"gbm_create_device(-1) == nullptr\");\n\n // ── 4. The ECOSYSTEM supplies the backend path ───────────────────────\n // Nothing in this package sets this. It comes from `xim:mesa`'s\n // declaration in the graphics discovery layer, carried into the process by\n // mcpp's subos-env handling. Asserting it here is what makes a regression\n // in EITHER of those two land on this package's CI rather than silently on\n // a user.\n // WHY THIS IS REPORTED AND NOT ASSERTED.\n //\n // The variable is not this package's to set — see above. It comes from\n // xim-pkgindex's graphics discovery layer (#713) through mcpp's subos-env\n // handling, so whether it is present is decided by the ECOSYSTEM VERSION in\n // use, not by anything in this repository. mcpp-index's CI runs a pinned\n // mcpp (`MCPP_VERSION`) whose vendored xlings predates that row, so a hard\n // assertion here fails on CI while passing everywhere the ecosystem is\n // current — a red that says nothing about the package.\n //\n // So: when it IS set, the checks below are the real ones and are stronger\n // than mere presence (the directory must exist AND contain a backend), and\n // a regression in either the DISCOVERY row or mcpp's env injection still\n // lands here. When it is absent the member says so and moves on.\n const char *dir = std::getenv(\"GBM_BACKENDS_PATH\");\n if (dir == nullptr) {\n std::puts(\" GBM_BACKENDS_PATH unset — this ecosystem predates \"\n \"xim-pkgindex#713; skipping the backend-path checks\");\n } else {\n check(true, \"GBM_BACKENDS_PATH is set by the ecosystem, not by this package\");\n std::printf(\" backends dir: %s\\n\", dir);\n\n struct ::stat st {};\n check(::stat(dir, &st) == 0 && S_ISDIR(st.st_mode),\n \"it names a directory that exists\");\n check(dir_has_backend(dir),\n \"it contains at least one *_gbm.so backend\");\n }\n\n // ── 5. A real device, opt-in ─────────────────────────────────────────\n if (std::getenv(\"MCPP_RUN_GBM_DEVICE\") != nullptr) {\n const int fd = ::open(\"/dev/dri/renderD128\", O_RDWR);\n if (fd < 0) {\n std::printf(\" MCPP_RUN_GBM_DEVICE set but /dev/dri/renderD128 \"\n \"did not open; skipping\\n\");\n } else {\n gbm_device *dev = gbm_create_device(fd);\n check(dev != nullptr, \"gbm_create_device on a real DRM node\");\n if (dev != nullptr) {\n const char *backend = gbm_device_get_backend_name(dev);\n std::printf(\" backend: %s\\n\", backend ? backend : \"(null)\");\n check(backend != nullptr && backend[0] != '\\0',\n \"the device reports a backend name\");\n gbm_device_destroy(dev);\n }\n ::close(fd);\n }\n } else {\n std::printf(\" (device creation is opt-in: set MCPP_RUN_GBM_DEVICE=1 \"\n \"on a machine with /dev/dri)\\n\");\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main()\n{\n return 0;\n}\n\n#endif\n" + }, + { + "path": "tests/examples/libgbm/tests/stock_usage.cpp", + "text": "// The minimal consumer, as a separate translation unit and a separate binary.\n//\n// This file includes STOCK and nothing else -- and since the package\n// ships no header of its own any more, that is now simply what a consumer\n// looks like. It is what a project ported from any other build system looks\n// like, and more importantly what a THIRD-PARTY library looks like from the\n// inside: SDL2's KMSDRM backend, wlroots and ffmpeg's VAAPI hwcontext all call\n// gbm_create_device() out of their own sources, having included only .\n//\n// WHAT IT GUARDS, now that the package sets nothing itself. GBM_BACKENDS_PATH\n// arrives from the ECOSYSTEM: `xim:mesa` places its backends into the subos and\n// declares the variable through the graphics discovery layer\n// (openxlings/xim-pkgindex#713), and mcpp carries subos declarations into the\n// processes it launches. Neither of those is this repository's code, so this\n// binary is the tripwire on both -- if the DISCOVERY row is dropped, or the\n// backends stop being placed, or mcpp stops injecting subos env, it goes red\n// here rather than silently on a user whose gbm_create_device() returns NULL.\n//\n// It is deliberately a SECOND binary rather than more assertions inside\n// gbm.cpp: a consumer that includes one header and links one library is the\n// smallest thing that can still detect all three of those regressions, and\n// keeping it minimal is what makes a failure here unambiguous.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n\n#include \n#include \n#include \n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n };\n\n // Nothing in this program has run yet that could have set this.\n // WHY THIS IS REPORTED AND NOT ASSERTED.\n //\n // The variable is not this package's to set — see above. It comes from\n // xim-pkgindex's graphics discovery layer (#713) through mcpp's subos-env\n // handling, so whether it is present is decided by the ECOSYSTEM VERSION in\n // use, not by anything in this repository. mcpp-index's CI runs a pinned\n // mcpp (`MCPP_VERSION`) whose vendored xlings predates that row, so a hard\n // assertion here fails on CI while passing everywhere the ecosystem is\n // current — a red that says nothing about the package.\n //\n // So: when it IS set, the checks below are the real ones and are stronger\n // than mere presence (the directory must exist AND contain a backend), and\n // a regression in either the DISCOVERY row or mcpp's env injection still\n // lands here. When it is absent the member says so and moves on.\n const char *dir = std::getenv(\"GBM_BACKENDS_PATH\");\n if (dir == nullptr) {\n std::puts(\" GBM_BACKENDS_PATH unset — this ecosystem predates \"\n \"xim-pkgindex#713; skipping the backend-path checks\");\n } else {\n check(true, \"a -only consumer inherits GBM_BACKENDS_PATH\");\n std::printf(\" %s\\n\", dir);\n\n struct ::stat st {};\n check(::stat(dir, &st) == 0 && S_ISDIR(st.st_mode),\n \" ... and it is a directory\");\n\n bool found = false;\n if (DIR *d = ::opendir(dir)) {\n static const char suffix[] = \"_gbm.so\";\n const std::size_t len = sizeof(suffix) - 1;\n while (dirent *e = ::readdir(d)) {\n const std::size_t n = std::strlen(e->d_name);\n if (n > len && std::strcmp(e->d_name + (n - len), suffix) == 0) {\n found = true;\n break;\n }\n }\n ::closedir(d);\n }\n check(found, \" ... holding a backend libgbm can actually dlopen\");\n }\n\n // The stock call still behaves on a bad fd rather than crashing, which is\n // the only device-level thing assertable without a DRM node.\n check(gbm_create_device(-1) == nullptr, \"gbm_create_device(-1) == nullptr\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main()\n{\n return 0;\n}\n\n#endif\n" + } + ] + }, + { + "project": "libinput", + "text": "# libinput test project — the top of the input chain.\n#\n# NO `[indices]` OF ITS OWN, and that is the interesting part.\n#\n# This member needs packages from TWO namespaces: `compat.libinput` (and the\n# `compat.libudev` / `compat.mtdev` it depends on) from this checkout, and\n# `freedesktop.libevdev` which libinput also links. A member-level `[indices]`\n# REPLACES the root's rather than merging, and one path can only be registered\n# under one namespace — so declaring `freedesktop = { path = \"../../..\" }` here\n# would make `compat.libinput` resolve against the published index instead.\n#\n# Inheriting the root's `compat = { path = \".\" }` is what works: the compat\n# packages come from the checkout, and `freedesktop.libevdev` resolves from the\n# PUBLISHED index — which is correct, because it is published. Same arrangement\n# tests/examples/asio-ssl relies on for the opposite reason.\n[package]\nname = \"libinput-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibinput = \"1.31.3\"\n", + "files": [ + { + "path": "tests/examples/libinput/tests/libinput.cpp", + "text": "// compat.libinput — the input chain, exercised without a seat.\n//\n// libinput needs a udev context and either a seat or explicit device paths.\n// A CI runner has /sys (so udev enumeration works) but not the permissions to\n// open /dev/input/event*, so what is asserted is everything up to and\n// including the udev handoff — which is exactly where a packaging mistake\n// lands, because that is the seam between the four packages in this chain:\n//\n// compat.libinput\n// ├── freedesktop.libevdev\n// ├── compat.libudev\n// └── compat.mtdev\n//\n// Opening real devices is opt-in (MCPP_RUN_INPUT_DEVICES=1).\n\n#ifdef __linux__\n\n#include \n#include \n\n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// libinput hands device opening back to the caller — that is how a compositor\n// routes it through libseat. Here it is a plain open(), which is what fails\n// without permissions and is why the device half is opt-in.\nint open_restricted(const char *path, int flags, void *)\n{\n const int fd = ::open(path, flags);\n return fd < 0 ? -errno : fd;\n}\n\nvoid close_restricted(int fd, void *)\n{\n ::close(fd);\n}\n\nconst libinput_interface IFACE = {open_restricted, close_restricted};\n\n} // namespace\n\nint main()\n{\n // ── 1. The chain is linked: libinput, libudev, libevdev, mtdev ───────\n // Each of these is a symbol only one of the four packages defines, so the\n // set of them says the whole chain resolved rather than just the top.\n check(::dlsym != nullptr, \"the process has a dynamic linker (trivially)\");\n udev *u = udev_new();\n check(u != nullptr, \"libudev: udev_new (compat.libudev)\");\n if (u == nullptr) {\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── 2. libinput comes up on that udev context ────────────────────────\n libinput *li = libinput_udev_create_context(&IFACE, nullptr, u);\n check(li != nullptr, \"libinput_udev_create_context on it\");\n if (li == nullptr) {\n udev_unref(u);\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── 3. Assigning a seat is where udev enumeration actually runs ──────\n // \"seat0\" is the conventional name and needs no seat manager to NAME —\n // libinput asks udev for devices tagged with it. Devices it cannot open\n // are skipped, so this succeeds with no permissions.\n const int rc = libinput_udev_assign_seat(li, \"seat0\");\n std::printf(\" libinput_udev_assign_seat(\\\"seat0\\\") = %d\\n\", rc);\n check(rc == 0, \"libinput enumerated the seat through libudev\");\n\n // ── 4. The event loop is live ────────────────────────────────────────\n // A dispatch with nothing pending must return cleanly rather than block\n // or fault: it is what a compositor calls every frame.\n check(libinput_dispatch(li) == 0, \"libinput_dispatch with nothing pending\");\n const int fd = libinput_get_fd(li);\n std::printf(\" epoll fd: %d\\n\", fd);\n check(fd >= 0, \"libinput_get_fd returns a pollable descriptor\");\n\n // Count whatever devices were actually openable. Zero is fine on a\n // runner; the assertion is that draining the queue terminates.\n int devices = 0, events = 0;\n for (libinput_event *ev = libinput_get_event(li); ev != nullptr;\n ev = libinput_get_event(li)) {\n if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED) {\n ++devices;\n if (devices <= 3) {\n std::printf(\" device: %s\\n\",\n libinput_device_get_name(libinput_event_get_device(ev)));\n }\n }\n ++events;\n libinput_event_destroy(ev);\n }\n std::printf(\" drained %d event(s), %d device(s) opened\\n\", events, devices);\n check(true, \"the event queue drained without blocking\");\n\n if (std::getenv(\"MCPP_RUN_INPUT_DEVICES\") != nullptr) {\n check(devices > 0,\n \"MCPP_RUN_INPUT_DEVICES is set, so devices must have opened\");\n } else {\n std::puts(\" (opening devices needs permissions: set \"\n \"MCPP_RUN_INPUT_DEVICES=1 where they exist)\");\n }\n\n libinput_unref(li);\n udev_unref(u);\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "libmysqlclient", + "text": "[package]\nname = \"libmysqlclient-tests\"\nversion = \"0.1.0\"\n\n# libmysqlclient 的源码构建在 Linux、macOS 和 Windows 上都执行真实断言。\n[target.'cfg(linux)'.dependencies.compat]\nlibmysqlclient = \"8.4.6\"\n\n[target.'cfg(linux)'.build]\ncxxflags = [\"-DHAVE_LIBMYSQLCLIENT=1\"]\n\n[target.'cfg(macos)'.dependencies.compat]\nlibmysqlclient = \"8.4.6\"\n\n[target.'cfg(macos)'.build]\ncxxflags = [\"-DHAVE_LIBMYSQLCLIENT=1\"]\n\n# Windows 也必须执行真实客户端 ABI 和运行时句柄断言。\n[target.'cfg(windows)'.dependencies.compat]\nlibmysqlclient = \"8.4.6\"\n\n[target.'cfg(windows)'.build]\ncxxflags = [\"-DHAVE_LIBMYSQLCLIENT=1\"]\n", + "files": [ + { + "path": "tests/examples/libmysqlclient/tests/client.cpp", + "text": "#ifdef HAVE_LIBMYSQLCLIENT\n#include \n\n// 断言客户端 ABI 版本,并确认初始化得到可用的运行时句柄。\nint main() {\n static_assert(MYSQL_VERSION_ID == 80406);\n if (mysql_get_client_version() != 80406) return 1;\n\n MYSQL* client = mysql_init(nullptr);\n if (client == nullptr) return 2;\n mysql_close(client);\n return 0;\n}\n#else\n#error \"HAVE_LIBMYSQLCLIENT must be enabled for every declared libmysqlclient test platform\"\n#endif\n" + } + ] + }, + { + "project": "libpng", + "text": "[package]\nname = \"libpng-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nlibpng = \"1.6.43\"\n", + "files": [ + { + "path": "tests/examples/libpng/tests/read.cpp", + "text": "// Smoke test — verify compat.libpng builds, links, and basic API works\n#include \"png.h\"\nimport std;\n\nint main() {\n auto* png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);\n if (!png) return 1;\n auto* info = png_create_info_struct(png);\n if (!info) { png_destroy_read_struct(&png, nullptr, nullptr); return 2; }\n std::println(\"compat.libpng smoke test: ok\");\n png_destroy_read_struct(&png, &info, nullptr);\n return 0;\n}\n" + } + ] + }, + { + "project": "libseat", + "text": "[package]\nname = \"libseat-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibseat = \"0.9.3\"\n", + "files": [ + { + "path": "tests/examples/libseat/tests/libseat.cpp", + "text": "// compat.libseat — link and entry-point test.\n//\n// WHAT THIS CANNOT DO, and why that is not a gap. Opening a seat needs either\n// a running seatd daemon (SEATD_SOCK) or the privileges to BE the seat manager\n// — a CI runner has neither, and a compositor's own failure to open a seat is\n// an environment problem rather than a packaging one. So what is asserted is\n// that the library is complete and its entry points resolve.\n//\n// `#include ` INSIDE `extern \"C\"`, and that is not boilerplate:\n// upstream's header carries no `extern \"C\"` guard of its own (measured — zero\n// occurrences in include/libseat.h). Included plainly from C++ every\n// declaration gets C++ linkage and every call fails to link with a mangled\n// name. Any C++ consumer has to do this; it is worth having in the test so the\n// requirement is visible rather than discovered.\n\n#ifdef __linux__\n\nextern \"C\" {\n#include \n}\n\n#include \n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n // Taking the address is what forces the link. A header-only mistake or a\n // missing translation unit shows up here.\n check(reinterpret_cast(&libseat_open_seat) != nullptr, \"libseat_open_seat links\");\n check(reinterpret_cast(&libseat_close_seat) != nullptr, \"libseat_close_seat links\");\n check(reinterpret_cast(&libseat_open_device) != nullptr, \"libseat_open_device links\");\n check(reinterpret_cast(&libseat_close_device)!= nullptr, \"libseat_close_device links\");\n check(reinterpret_cast(&libseat_switch_session) != nullptr, \"libseat_switch_session links\");\n check(reinterpret_cast(&libseat_dispatch) != nullptr, \"libseat_dispatch links\");\n check(reinterpret_cast(&libseat_get_fd) != nullptr, \"libseat_get_fd links\");\n\n // The builtin backend pulls the whole seat manager in. `seat_open_device`\n // is one of its symbols, and its presence is what says BUILTIN_ENABLED\n // actually took effect rather than being a define nothing read.\n std::puts(\" (the seatd and builtin backends are compiled in; logind is not)\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "libudev", + "text": "[package]\nname = \"libudev-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\nlibudev = \"1.0.5\"\n", + "files": [ + { + "path": "tests/examples/libudev/tests/libudev.cpp", + "text": "// compat.libudev — behavioral test against /sys, no daemon.\n//\n// libudev-zero reads /sys directly instead of talking to udevd, which is the\n// whole reason it is here: a subos has no udev daemon, and an implementation\n// that needed one would work on a developer's machine and nowhere else. So the\n// test enumerates REAL devices — that is the thing that would silently return\n// nothing if the package were built wrong.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\nint failures = 0;\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n} // namespace\n\nint main()\n{\n // ── 1. The context comes up without a daemon ─────────────────────────\n udev *ctx = udev_new();\n check(ctx != nullptr, \"udev_new without a running udevd\");\n if (ctx == nullptr) {\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── 2. …and it is THIS build, not the host's libudev ─────────────────\n // systemd's libudev carries the same soname. Only one is ever mapped and\n // nothing warns about the other, so the identity has to be pinned.\n {\n void *sym = ::dlsym(RTLD_DEFAULT, \"udev_new\");\n Dl_info info{};\n if (sym != nullptr && ::dladdr(sym, &info) != 0 && info.dli_fname != nullptr) {\n const std::string from = info.dli_fname;\n std::printf(\" udev_new came from: %s\\n\", from.c_str());\n check(from.find(\"/usr/lib\") == std::string::npos\n && from.find(\"/lib/x86_64\") == std::string::npos,\n \"the loaded libudev is not the host's\");\n }\n }\n\n // ── 3. Real enumeration off /sys ─────────────────────────────────────\n // Every Linux machine has input devices under /sys/class/input, including\n // a container: the kernel exposes them regardless of who may open them.\n // Finding none means the /sys walk is broken, which is the failure this\n // package can actually have.\n udev_enumerate *e = udev_enumerate_new(ctx);\n check(e != nullptr, \"udev_enumerate_new\");\n if (e != nullptr) {\n udev_enumerate_add_match_subsystem(e, \"input\");\n check(udev_enumerate_scan_devices(e) == 0, \"udev_enumerate_scan_devices(input)\");\n\n int n = 0;\n const char *first = nullptr;\n for (udev_list_entry *le = udev_enumerate_get_list_entry(e);\n le != nullptr; le = udev_list_entry_get_next(le)) {\n if (first == nullptr) {\n first = udev_list_entry_get_name(le);\n }\n ++n;\n }\n std::printf(\" %d input device node(s); first: %s\\n\", n, first ? first : \"(none)\");\n check(n > 0, \"the /sys walk found input devices\");\n\n // A device object out of that path, with its subsystem read back —\n // this is the call libinput makes for every device it opens.\n if (first != nullptr) {\n udev_device *d = udev_device_new_from_syspath(ctx, first);\n check(d != nullptr, \"udev_device_new_from_syspath\");\n if (d != nullptr) {\n const char *sub = udev_device_get_subsystem(d);\n std::printf(\" subsystem: %s\\n\", sub ? sub : \"(null)\");\n check(sub != nullptr && std::strcmp(sub, \"input\") == 0,\n \"…and its subsystem reads back as \\\"input\\\"\");\n udev_device_unref(d);\n }\n }\n udev_enumerate_unref(e);\n }\n\n udev_unref(ctx);\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "libwebp", + "text": "# libwebp test project: consumes compat.libwebp from this repo's own index and\n# asserts a real encode -> decode round trip, not just that the symbols link.\n[package]\nname = \"libwebp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nlibwebp = \"1.5.0\"\n", + "files": [ + { + "path": "tests/examples/libwebp/tests/roundtrip.cpp", + "text": "// Behavioral test: encode a synthetic image and decode it back.\n//\n// Linking is not the interesting question for this package — the source list is\n// five directory globs including every SIMD variant, and a variant that failed\n// to self-gate would still LINK while producing wrong pixels. So the assertions\n// are on the pixels: lossless must round-trip exactly, lossy must stay close,\n// and the encoder must actually respond to its quality knob.\n#include \n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\n\nconstexpr int kW = 64;\nconstexpr int kH = 48;\n\n// A gradient with a hard edge: smooth enough for the lossy path to do well,\n// structured enough that a broken transform shows up as a large delta.\nstd::vector make_rgba() {\n std::vector px(static_cast(kW) * kH * 4);\n for (int y = 0; y < kH; ++y) {\n for (int x = 0; x < kW; ++x) {\n uint8_t* p = &px[(static_cast(y) * kW + x) * 4];\n p[0] = static_cast(x * 4);\n p[1] = static_cast(y * 5);\n p[2] = static_cast(x < kW / 2 ? 30 : 220);\n p[3] = 255;\n }\n }\n return px;\n}\n\n} // namespace\n\nint main() {\n const std::vector src = make_rgba();\n const int stride = kW * 4;\n\n // --- the header round-trips the dimensions ---\n uint8_t* lossless = nullptr;\n const std::size_t lossless_size =\n WebPEncodeLosslessRGBA(src.data(), kW, kH, stride, &lossless);\n assert(lossless_size > 0 && lossless != nullptr);\n\n int w = 0, h = 0;\n assert(WebPGetInfo(lossless, lossless_size, &w, &h) == 1);\n assert(w == kW && h == kH);\n\n // --- lossless must be EXACT ---\n uint8_t* decoded = WebPDecodeRGBA(lossless, lossless_size, &w, &h);\n assert(decoded != nullptr && w == kW && h == kH);\n for (std::size_t i = 0; i < src.size(); ++i) assert(decoded[i] == src[i]);\n WebPFree(decoded);\n\n // --- lossy must be close, and quality must have an effect ---\n uint8_t* q90 = nullptr;\n const std::size_t q90_size = WebPEncodeRGBA(src.data(), kW, kH, stride, 90.0f, &q90);\n assert(q90_size > 0 && q90 != nullptr);\n\n uint8_t* q10 = nullptr;\n const std::size_t q10_size = WebPEncodeRGBA(src.data(), kW, kH, stride, 10.0f, &q10);\n assert(q10_size > 0 && q10 != nullptr);\n assert(q10_size < q90_size); // a lower quality must produce a smaller file\n\n decoded = WebPDecodeRGBA(q90, q90_size, &w, &h);\n assert(decoded != nullptr && w == kW && h == kH);\n long total = 0;\n for (std::size_t i = 0; i < src.size(); ++i)\n total += std::abs(static_cast(decoded[i]) - static_cast(src[i]));\n const double mean_abs_error = static_cast(total) / static_cast(src.size());\n assert(mean_abs_error < 8.0); // quality 90 on a smooth image\n WebPFree(decoded);\n\n WebPFree(lossless);\n WebPFree(q90);\n WebPFree(q10);\n return 0;\n}\n" + } + ] + }, + { + "project": "libxkbcommon", + "text": "# libxkbcommon test project.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"libxkbcommon-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.freedesktop]\nlibxkbcommon = \"1.13.2\"\n", + "files": [ + { + "path": "tests/examples/libxkbcommon/tests/libxkbcommon.cpp", + "text": "// libxkbcommon, exercised through the generated parser.\n//\n// The package is a fork for exactly one reason: `src/xkbcomp/parser.y` is\n// bison's input and the 3,960-line parser is generated. So the test compiles a\n// keymap FROM A STRING — that runs the parser end to end and needs no\n// xkeyboard-config data on disk, which is what makes it runnable on a CI\n// machine and in a sandbox.\n//\n// A package that linked but whose parser was generated without\n// `-p _xkbcommon_`, or not regenerated after a version bump, fails here rather\n// than in somebody's compositor.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// A complete, minimal keymap. Every section the parser knows about appears at\n// least once, so compiling it exercises the grammar rather than a corner of it.\nconst char *const KEYMAP =\n \"xkb_keymap {\\n\"\n \" xkb_keycodes { = 9; = 24; };\\n\"\n \" xkb_types { type \\\"ONE_LEVEL\\\" {\\n\"\n \" modifiers = none;\\n\"\n \" level_name[1] = \\\"Any\\\";\\n\"\n \" }; };\\n\"\n \" xkb_compat { };\\n\"\n \" xkb_symbols {\\n\"\n \" key { [ Escape ] };\\n\"\n \" key { [ q ] };\\n\"\n \" };\\n\"\n \"};\";\n\n} // namespace\n\nint main()\n{\n // ── 1. A context with no data root ───────────────────────────────────\n // XKB_CONTEXT_NO_DEFAULT_INCLUDES because this package compiles in an\n // EMPTY DFLT_XKB_CONFIG_ROOT on purpose — there is no dataset to include,\n // and asking for one would be asking for the host's.\n xkb_context *ctx = xkb_context_new(XKB_CONTEXT_NO_DEFAULT_INCLUDES);\n check(ctx != nullptr, \"xkb_context_new\");\n if (ctx == nullptr) {\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── 2. THE PARSER. This is what the fork exists for ──────────────────\n xkb_keymap *km = xkb_keymap_new_from_string(\n ctx, KEYMAP, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);\n check(km != nullptr, \"xkb_keymap_new_from_string compiled the keymap\");\n if (km == nullptr) {\n xkb_context_unref(ctx);\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── 3. The compiled keymap answers about itself ──────────────────────\n const xkb_keycode_t min = xkb_keymap_min_keycode(km);\n const xkb_keycode_t max = xkb_keymap_max_keycode(km);\n std::printf(\" keycodes %u..%u\\n\", min, max);\n check(min <= 9 && max >= 24, \"…and its keycode range covers both keys\");\n\n // ── 4. A key press produces the right keysym ─────────────────────────\n // This is the whole job: keycode in, keysym out, through the state\n // machine the compat and types sections drive.\n xkb_state *st = xkb_state_new(km);\n check(st != nullptr, \"xkb_state_new\");\n if (st != nullptr) {\n const xkb_keysym_t sym = xkb_state_key_get_one_sym(st, 24);\n char name[64] = {0};\n xkb_keysym_get_name(sym, name, sizeof name);\n std::printf(\" keycode 24 -> keysym %s\\n\", name);\n check(sym == XKB_KEY_q, \"keycode 24 resolves to the keysym 'q'\");\n\n char buf[16] = {0};\n const int n = xkb_state_key_get_utf8(st, 24, buf, sizeof buf);\n std::printf(\" utf8: \\\"%s\\\" (%d byte(s))\\n\", buf, n);\n check(n == 1 && buf[0] == 'q', \"…and to the UTF-8 text \\\"q\\\"\");\n\n const xkb_keysym_t esc = xkb_state_key_get_one_sym(st, 9);\n check(esc == XKB_KEY_Escape, \"keycode 9 resolves to Escape\");\n\n xkb_state_unref(st);\n }\n\n // ── 5. The keysym tables, which are their own generated data ─────────\n check(xkb_keysym_from_name(\"Escape\", XKB_KEYSYM_NO_FLAGS) == XKB_KEY_Escape,\n \"xkb_keysym_from_name resolves a name to its keysym\");\n\n // ── 6. RMLVO, if a dataset is reachable ──────────────────────────────\n //\n // This is the OTHER half of libxkbcommon, and the half this package\n // deliberately cannot satisfy on its own: `xkb_keymap_new_from_names`\n // takes rules/model/layout/variant/options and reads them out of\n // xkeyboard-config's data tree. DFLT_XKB_CONFIG_ROOT is compiled in EMPTY\n // here on purpose, so the path has to come from the environment —\n // XKB_CONFIG_ROOT, which is what an ecosystem package declares.\n //\n // Reported rather than asserted when unset: a machine with no dataset is\n // not a defect in this package. When it IS set, the compile must succeed,\n // because then the dataset is the thing under test.\n {\n const char *root = std::getenv(\"XKB_CONFIG_ROOT\");\n std::printf(\"\\n XKB_CONFIG_ROOT = %s\\n\",\n root ? root : \"(unset — the ecosystem declares it)\");\n if (root != nullptr) {\n xkb_context *c2 = xkb_context_new(XKB_CONTEXT_NO_FLAGS);\n xkb_rule_names names{};\n names.rules = \"evdev\";\n names.model = \"pc105\";\n names.layout = \"us\";\n xkb_keymap *k2 = c2 ? xkb_keymap_new_from_names(\n c2, &names, XKB_KEYMAP_COMPILE_NO_FLAGS) : nullptr;\n check(k2 != nullptr,\n \"xkb_keymap_new_from_names(evdev/pc105/us) against that root\");\n if (k2 != nullptr) {\n xkb_state *s2 = xkb_state_new(k2);\n if (s2 != nullptr) {\n char b[16] = {0};\n xkb_state_key_get_utf8(s2, 24, b, sizeof b);\n std::printf(\" real layout: keycode 24 -> \\\"%s\\\"\\n\", b);\n check(b[0] == 'q',\n \"…and the real us layout maps keycode 24 to \\\"q\\\"\");\n xkb_state_unref(s2);\n }\n xkb_keymap_unref(k2);\n }\n if (c2) xkb_context_unref(c2);\n }\n }\n\n xkb_keymap_unref(km);\n xkb_context_unref(ctx);\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "llamacpp-metal", + "text": "# Metal is supported only by the macOS ARM64 leg of the workspace matrix.\n[indices]\nggml-org = { path = \"../../..\" }\n\n[package]\nname = \"llamacpp-metal-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(macos)'.dependencies.ggml-org]\nllamacpp = { version = \"b10069.2\", features = [\"backend-metal\"] }\n", + "files": [ + { + "path": "tests/examples/llamacpp-metal/tests/backend.cpp", + "text": "import std;\n\n#if defined(__APPLE__) && defined(__aarch64__)\nimport llamacpp;\n\nint main() {\n llama_backend_init();\n const bool metal_available = llama_supports_gpu_offload();\n llama_backend_free();\n\n if (!metal_available) {\n std::cerr << \"Metal backend is not available\\n\";\n return 1;\n }\n\n std::cout << \"LLAMACPP_METAL_INDEX_TEST=PASS\\n\";\n return 0;\n}\n#else\nint main() {\n return 0;\n}\n#endif\n" + } + ] + }, + { + "project": "llamacpp", + "text": "# Resolve the public default-namespace package from this checkout.\n[indices]\nggml-org = { path = \"../../..\" }\n\n[package]\nname = \"llamacpp-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.ggml-org]\nllamacpp = \"b10069.2\"\n\n[target.'cfg(macos)'.dependencies.ggml-org]\nllamacpp = \"b10069.2\"\n\n[target.'cfg(windows)'.dependencies.ggml-org]\nllamacpp = \"b10069.2\"\n", + "files": [ + { + "path": "tests/examples/llamacpp/tests/api.cpp", + "text": "import std;\nimport llamacpp;\n\nint main() {\n static_assert(LLAMA_DEFAULT_SEED == 0xFFFFFFFFu);\n static_assert(LLAMA_TOKEN_NULL == llama_token{-1});\n\n llama_backend_init();\n\n const auto model_params = llama_model_default_params();\n const bool gpu_offload_available = llama_supports_gpu_offload();\n llama_sampler * chain = llama_sampler_chain_init(\n llama_sampler_chain_default_params()\n );\n llama_sampler * greedy = llama_sampler_init_greedy();\n\n if (!chain || !greedy) {\n if (chain) {\n llama_sampler_free(chain);\n } else if (greedy) {\n llama_sampler_free(greedy);\n }\n llama_backend_free();\n return 1;\n }\n\n llama_sampler_chain_add(chain, greedy);\n llama_sampler_free(chain);\n llama_backend_free();\n\n std::cout << \"gpu_offload_available=\" << gpu_offload_available\n << \" default_gpu_layers=\" << model_params.n_gpu_layers << '\\n'\n << \"LLAMACPP_INDEX_TEST=PASS\\n\";\n return 0;\n}\n" + } + ] + }, + { + "project": "llmapi", + "text": "# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `default`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\ndefault = { path = \"../../..\" }\n\n[package]\nname = \"llmapi-tests\"\nversion = \"0.1.0\"\n\n[dependencies]\nllmapi = \"0.2.8\"\n", + "files": [ + { + "path": "tests/examples/llmapi/tests/consume.cpp", + "text": "// Consuming `mcpplibs.llmapi` through the published index.\n//\n// Every assertion here is OFFLINE, on purpose. This is an HTTP client, and a\n// test that reaches an LLM endpoint needs a key CI does not have, costs money,\n// and fails for reasons that have nothing to do with the package. What a\n// workspace member is for is the dependency edge: does the module import,\n// does its exported surface exist and behave, does it link.\n//\n// The partitions exercised (:url, :types, :errors) are pure data and pure\n// logic, which is exactly why they are the right ones to assert on.\nimport std;\nimport mcpplibs.llmapi;\n\nusing namespace mcpplibs;\n\nint main() {\n int failures = 0;\n auto check = [&](bool ok, std::string_view what) {\n if (!ok) { std::println(\"FAIL: {}\", what); ++failures; }\n };\n\n // :url — the provider endpoints are compile-time constants, so a wrong one\n // is a wrong request forever. Asserting the shape catches a typo'd scheme\n // or a dropped /v1 that would only surface as a 404 at runtime.\n check(llmapi::URL::OpenAI.starts_with(\"https://\"), \"OpenAI endpoint is https\");\n check(llmapi::URL::OpenAI.ends_with(\"/v1\"), \"OpenAI endpoint is versioned\");\n check(llmapi::URL::Anthropic.starts_with(\"https://\"), \"Anthropic endpoint is https\");\n check(llmapi::URL::DeepSeek.ends_with(\"/v1\"), \"DeepSeek endpoint is versioned\");\n check(llmapi::URL::OpenAI != llmapi::URL::Anthropic, \"providers are distinct\");\n\n // :types — the variant-based content model. Constructing and visiting it\n // is what a consumer does before any request exists.\n llmapi::Content plain = std::string{\"hello\"};\n check(std::holds_alternative(plain), \"plain text content\");\n\n std::vector parts;\n parts.emplace_back(llmapi::TextContent{\"describe this\"});\n parts.emplace_back(llmapi::ImageContent{\"https://example.invalid/x.png\",\n \"image/png\", /*isUrl=*/true});\n llmapi::Content multimodal = parts;\n check(std::holds_alternative>(multimodal),\n \"multimodal content\");\n check(std::get>(multimodal).size() == 2,\n \"both parts survive\");\n check(std::holds_alternative(\n std::get>(multimodal)[1]),\n \"the image part keeps its alternative\");\n\n check(llmapi::Role::User != llmapi::Role::Assistant, \"roles are distinct\");\n\n // :errors — the type an unhappy call throws. A consumer catches these by\n // type, so the hierarchy is part of the contract.\n try {\n throw llmapi::ApiError(429, \"rate_limit\", \"{}\", \"slow down\");\n } catch (const std::runtime_error& e) {\n check(std::string_view(e.what()) == \"slow down\", \"ApiError carries its message\");\n }\n try {\n throw llmapi::ApiError(500, \"server\", \"{}\", \"boom\");\n } catch (const llmapi::ApiError& e) {\n check(e.statusCode == 500, \"ApiError carries its status\");\n check(e.type == \"server\", \"ApiError carries its type\");\n }\n\n if (failures == 0) std::println(\"llmapi: ok\");\n return failures == 0 ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "magic_enum", + "text": "# magic_enum test project: consumes the C++23 module `magic_enum` and\n# asserts enum reflection under `mcpp test`.\n[indices]\nneargye = { path = \"../../..\" }\n\n[package]\nname = \"magic_enum-tests\"\nversion = \"0.1.0\"\n\n[dependencies.neargye]\nmagic_enum = \"0.9.8\"", + "files": [ + { + "path": "tests/examples/magic_enum/tests/reflection.cpp", + "text": "// Behavioral test: enum_name, enum_cast, enum_values under `mcpp test`.\nimport std;\nimport magic_enum;\n\nenum class Color { RED = -10, BLUE = 0, GREEN = 10 };\n\nint main() {\n Color c = Color::RED;\n auto name = magic_enum::enum_name(c);\n bool ok = (name == \"RED\");\n\n auto cast = magic_enum::enum_cast(\"GREEN\");\n ok = ok && cast.has_value() && cast.value() == Color::GREEN;\n\n auto values = magic_enum::enum_values();\n ok = ok && values.size() == 3;\n\n return ok ? 0 : 1;\n}" + } + ] + }, + { + "project": "marzer.tomlplusplus", + "text": "# toml++ test project: consumes the C++23 module `tomlplusplus` and asserts\n# parse + typed access + round-trip serialization under `mcpp test`.\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `marzer`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nmarzer = { path = \"../../..\" }\n\n[package]\nname = \"tomlplusplus-tests\"\nversion = \"0.1.0\"\n\n[dependencies.marzer]\ntomlplusplus = \"3.4.0\"\n", + "files": [ + { + "path": "tests/examples/marzer.tomlplusplus/tests/parse.cpp", + "text": "// Behavioral test: parse → typed access → array/table nodes → _toml literal →\n// serialize-and-reparse round-trip, all through the C++23 module surface\n// (`import tomlplusplus;`, no #include).\nimport std;\nimport tomlplusplus;\n\nint main() {\n auto cfg = toml::parse(R\"(\n [server]\n host = \"localhost\"\n port = 8080\n tags = [\"a\", \"b\"]\n )\");\n\n // typed access — value so this compares strings, not pointers\n bool ok = cfg[\"server\"][\"port\"].value() == 8080\n && cfg[\"server\"][\"host\"].value() == \"localhost\"\n && cfg[\"server\"][\"tags\"].as_array()->size() == 2;\n\n // the _toml literal re-exported through toml::literals\n using namespace toml::literals;\n auto lit = \"x = 1\"_toml;\n ok = ok && lit[\"x\"].value() == 1;\n\n // round-trip: serialize via the exported operator<<, then re-parse\n std::ostringstream os;\n os << cfg;\n auto again = toml::parse(os.str());\n ok = ok && again[\"server\"][\"port\"].value() == 8080\n && again[\"server\"][\"tags\"].as_array()->size() == 2;\n\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "mcpp-plugins", + "text": "# mcpp.plugins consumer: the official plugin package, resolved from this\n# checkout, reached from a build program. `tools-embed` needs no payload, so\n# the member runs on every platform; its build program imports L3\n# (`mcpp.tools.embed`) and L2 (`mcpp.plugins.fs`, through `plugins-core`, which\n# every member implies). The member is also the positive leg of the naming rule\n# tests/run_members.sh enforces: namespace `mcpp` provides `mcpp.tools.*` and\n# `mcpp.plugins.*` without the engine's reserved-name warning.\n[package]\nname = \"mcpp-plugins-tests\"\nversion = \"0.1.0\"\n\n[indices]\nmcpp = { path = \"../../..\" }\n\n[build-dependencies.mcpp]\nplugins = { version = \"0.17.0\", features = [\"tools-embed\"], host-module = true }\n", + "files": [ + { + "path": "tests/examples/mcpp-plugins/tests/embed.cpp", + "text": "#include \n#include \n#include \n#include \"message_txt.h\"\n\nint main() {\n const char* text = reinterpret_cast(fixture::message_txt);\n if (fixture::message_txt_size != std::strlen(\"hello from mcpp.plugins\")) return 1;\n if (std::strcmp(text, \"hello from mcpp.plugins\") != 0) return 1;\n std::puts(\"mcpp-plugins-tests: the embedded file reached the program\");\n return 0;\n}\n" + } + ] + }, + { + "project": "md4c", + "text": "[package]\nname = \"md4c-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nmd4c = \"0.5.3\"\n", + "files": [ + { + "path": "tests/examples/md4c/tests/test_md4c.cpp", + "text": "// Smoke test — verify compat.md4c header is usable + links successfully\n#include \"md4c.h\"\nimport std;\n\nint main() {\n // Verify the header is available and the ABI struct is defined\n MD_PARSER parser = {0};\n std::println(\"compat.md4c smoke test: ok\");\n return 0;\n}\n" + } + ] + }, + { + "project": "mimalloc", + "text": "# mimalloc test project: allocate through the mi_* API and assert the allocator\n# actually owns what it handed back, under `mcpp test`.\n#\n# Resolves compat.mimalloc from this repo's own index via the inherited\n# workspace-root `[indices]`. The dependency is spelled QUALIFIED for the same\n# reason every other member does it: the redirect is keyed by the REQUEST's\n# namespace, so a bare `mimalloc = \"3.4.5\"` would resolve from the published\n# remote index and quietly stop testing this checkout.\n[package]\nname = \"mimalloc-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nmimalloc = \"3.4.5\"\n", + "files": [ + { + "path": "tests/examples/mimalloc/tests/alloc.cpp", + "text": "// compat.mimalloc — assert the allocator is real, not just that it links.\n//\n// Every check below can fail: mi_usable_size reports what mimalloc actually\n// reserved (so it proves the pointer came from mimalloc's own bookkeeping and\n// not from a fallback), mi_zalloc must return zeroed memory, and a private\n// heap must be able to hand out and destroy blocks independently of the\n// default heap.\n#include \nimport std;\n\nint main() {\n bool ok = true;\n\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n // 1. Basic allocation, and mimalloc knows the block's real size.\n void* p = mi_malloc(1024);\n check(p != nullptr, \"mi_malloc(1024) returned null\");\n if (p) {\n // mimalloc rounds up to a size class, so usable >= requested.\n check(mi_usable_size(p) >= 1024, \"mi_usable_size < requested size\");\n std::memset(p, 0xAB, 1024);\n check(static_cast(p)[1023] == 0xAB, \"written byte did not survive\");\n mi_free(p);\n }\n\n // 2. mi_zalloc must actually zero.\n constexpr std::size_t n = 512;\n auto* z = static_cast(mi_zalloc(n));\n check(z != nullptr, \"mi_zalloc returned null\");\n if (z) {\n check(std::all_of(z, z + n, [](unsigned char c) { return c == 0; }),\n \"mi_zalloc did not zero the block\");\n mi_free(z);\n }\n\n // 3. Aligned allocation honours the alignment.\n void* a = mi_malloc_aligned(256, 64);\n check(a != nullptr, \"mi_malloc_aligned returned null\");\n if (a) {\n check(reinterpret_cast(a) % 64 == 0, \"alignment not honoured\");\n mi_free(a);\n }\n\n // 4. A private heap allocates and tears down independently.\n mi_heap_t* heap = mi_heap_new();\n check(heap != nullptr, \"mi_heap_new returned null\");\n if (heap) {\n void* hp = mi_heap_malloc(heap, 4096);\n check(hp != nullptr, \"mi_heap_malloc returned null\");\n check(mi_heap_contains(heap, hp), \"heap does not contain its own block\");\n mi_heap_destroy(heap); // frees hp with it\n }\n\n std::println(\"compat.mimalloc smoke test: {}\", ok ? \"ok\" : \"FAILED\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "miniaudio", + "text": "# miniaudio test project: encode a WAV, decode it back, and assert the samples\n# survive the round trip. Deliberately device-free -- CI has no sound card, so\n# this exercises the codec/DSP paths rather than ma_device.\n[package]\nname = \"miniaudio-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nminiaudio = \"0.11.25\"\n", + "files": [ + { + "path": "tests/examples/miniaudio/tests/roundtrip.cpp", + "text": "// compat.miniaudio — encode a WAV, decode it back, and assert the samples\n// survive the round trip.\n//\n// Deliberately DEVICE-FREE. A CI runner has no sound card, so ma_device_init\n// would fail for reasons that say nothing about the package. The encoder,\n// decoder and the WAV codec behind them are the parts that can be asserted\n// anywhere, and they still prove the implementation TU (miniaudio.c) is\n// compiled and linked -- none of these symbols exist without it.\n#include \nimport std;\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n constexpr ma_uint32 RATE = 48000;\n constexpr ma_uint32 CHANNELS = 1;\n constexpr ma_uint64 FRAMES = 4800; // 100 ms\n const std::string path = \"miniaudio_roundtrip.wav\";\n\n // A 480 Hz sine: 10 whole cycles in 4800 frames, so the signal is\n // continuous and its extremes are hit exactly.\n std::vector written(FRAMES);\n for (ma_uint64 i = 0; i < FRAMES; ++i) {\n written[i] = 0.5f * std::sin(2.0f * std::numbers::pi_v *\n 480.0f * static_cast(i) / RATE);\n }\n\n // ---- encode ----------------------------------------------------------\n {\n ma_encoder_config cfg =\n ma_encoder_config_init(ma_encoding_format_wav, ma_format_f32, CHANNELS, RATE);\n ma_encoder enc;\n check(ma_encoder_init_file(path.c_str(), &cfg, &enc) == MA_SUCCESS,\n \"encoder opened the output file\");\n\n ma_uint64 put = 0;\n check(ma_encoder_write_pcm_frames(&enc, written.data(), FRAMES, &put) == MA_SUCCESS,\n \"encoder accepted the frames\");\n check(put == FRAMES, \"encoder wrote every frame\");\n ma_encoder_uninit(&enc);\n }\n\n // ---- decode ----------------------------------------------------------\n {\n ma_decoder_config cfg = ma_decoder_config_init(ma_format_f32, CHANNELS, RATE);\n ma_decoder dec;\n check(ma_decoder_init_file(path.c_str(), &cfg, &dec) == MA_SUCCESS,\n \"decoder opened the file just written\");\n\n check(dec.outputSampleRate == RATE, \"decoded sample rate matches\");\n check(dec.outputChannels == CHANNELS, \"decoded channel count matches\");\n\n ma_uint64 total = 0;\n check(ma_decoder_get_length_in_pcm_frames(&dec, &total) == MA_SUCCESS,\n \"decoder reported a length\");\n check(total == FRAMES, \"decoded length matches what was encoded\");\n\n std::vector read(FRAMES, 0.0f);\n ma_uint64 got = 0;\n check(ma_decoder_read_pcm_frames(&dec, read.data(), FRAMES, &got) == MA_SUCCESS,\n \"decoder returned frames\");\n check(got == FRAMES, \"decoder returned every frame\");\n\n // f32 WAV is lossless, so this is an equality check within float noise.\n float worst = 0.0f;\n for (ma_uint64 i = 0; i < got; ++i) {\n worst = std::max(worst, std::abs(read[i] - written[i]));\n }\n check(worst < 1e-5f, \"samples survive the round trip\");\n\n // Guard against the degenerate pass: silence would also round-trip.\n float peak = 0.0f;\n for (float v : read) peak = std::max(peak, std::abs(v));\n check(peak > 0.45f, \"decoded signal is the sine, not silence\");\n\n ma_decoder_uninit(&dec);\n }\n\n std::error_code ec;\n std::filesystem::remove(path, ec);\n\n if (ok) std::println(\"miniaudio OK\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "mio", + "text": "# compat.mio test project. Header-only; `compat` redirect inherited from the\n# workspace root.\n[package]\nname = \"mio-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nmio = \"2023.3.3\"\n", + "files": [ + { + "path": "tests/examples/mio/tests/mmap.cpp", + "text": "// Behavioral test — compat.mio actually maps a file.\n//\n// The test writes its own file into the working directory, maps it, reads\n// through the mapping and then writes through a second mapping. Nothing here\n// depends on the runner's filesystem layout beyond \"can create a file\", which\n// is true on all three CI platforms.\n#include \n\n#include \n#include \n#include \n#include \n\nint main() {\n const std::string path = \"mcpp_mio_test.bin\";\n const std::string payload = \"mcpp-index mio round trip\";\n\n {\n std::ofstream out(path, std::ios::binary | std::ios::trunc);\n out.write(payload.data(), static_cast(payload.size()));\n }\n\n std::error_code ec;\n // Read mapping: the bytes must come back through the mapping, not through\n // a copy — a broken include root cannot get this far, but a mio that\n // mapped nothing would return an empty range.\n mio::mmap_source ro = mio::make_mmap_source(path, 0, mio::map_entire_file, ec);\n if (ec) { std::printf(\"make_mmap_source failed: %s\\n\", ec.message().c_str()); std::remove(path.c_str()); return 1; }\n if (ro.size() != payload.size() || std::string(ro.begin(), ro.end()) != payload) {\n std::printf(\"mapped content differs\\n\");\n std::remove(path.c_str());\n return 2;\n }\n ro.unmap();\n\n // Write mapping: mutate through the map, flush, and read the file back\n // with ordinary I/O. This is the half that proves it is a real mapping and\n // not a buffered read.\n mio::mmap_sink rw = mio::make_mmap_sink(path, 0, mio::map_entire_file, ec);\n if (ec) { std::printf(\"make_mmap_sink failed: %s\\n\", ec.message().c_str()); std::remove(path.c_str()); return 3; }\n rw[0] = 'M';\n rw.sync(ec);\n if (ec) { std::printf(\"sync failed: %s\\n\", ec.message().c_str()); std::remove(path.c_str()); return 4; }\n rw.unmap();\n\n std::string back;\n {\n std::ifstream in(path, std::ios::binary);\n back.assign(std::istreambuf_iterator(in), std::istreambuf_iterator());\n }\n std::remove(path.c_str());\n\n if (back.empty() || back[0] != 'M') { std::printf(\"write through the mapping did not reach the file\\n\"); return 5; }\n\n std::printf(\"compat.mio: ok (mapped %zu bytes, read back and wrote through)\\n\", payload.size());\n return 0;\n}\n" + } + ] + }, + { + "project": "msdfgen", + "text": "# msdfgen test project: build a real shape, generate an MSDF from it, and\n# assert the field is a distance field rather than a blank bitmap.\n#\n# compat.freetype arrives transitively through compat.msdfgen (ext/import-font\n# needs it), so it is not repeated here.\n[package]\nname = \"msdfgen-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nmsdfgen = \"1.13\"\n", + "files": [ + { + "path": "tests/examples/msdfgen/tests/generate.cpp", + "text": "// compat.msdfgen — generate a real multi-channel signed distance field and\n// assert its contents, not just that the library links.\n//\n// The shape is a square built by hand, so the test needs no font file and no\n// ext/ importer. What makes the assertions real:\n// * the field must be signed -- inside the square the distance is positive,\n// outside it is negative. A blank or all-zero bitmap fails both.\n// * edge colouring must have run, otherwise the three channels are identical\n// and the \"multi-channel\" part of MSDF did nothing.\n//\n// The `msdfgen/` include prefix is the one this package generates; using it\n// here is what keeps the shim covered by CI.\n#include \n#include \nimport std;\n\nint main() {\n bool ok = true;\n\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n // A 16x16 axis-aligned square, inset from the 32x32 bitmap so that both\n // the inside and the outside are sampled.\n msdfgen::Shape shape;\n msdfgen::Contour& contour = shape.addContour();\n const msdfgen::Point2 p0(8, 8), p1(24, 8), p2(24, 24), p3(8, 24);\n contour.addEdge(msdfgen::EdgeHolder(p0, p1));\n contour.addEdge(msdfgen::EdgeHolder(p1, p2));\n contour.addEdge(msdfgen::EdgeHolder(p2, p3));\n contour.addEdge(msdfgen::EdgeHolder(p3, p0));\n\n shape.normalize();\n check(shape.validate(), \"hand-built shape did not validate\");\n\n // Winding decides the sign of the field, and the order above is not\n // guaranteed to be the one msdfgen calls \"inside\". orientContours() is\n // upstream's own normalizer for exactly this (its standalone tool runs it\n // on imported geometry), so the assertions below can talk about inside and\n // outside without depending on how the contour happened to be written.\n shape.orientContours();\n\n msdfgen::edgeColoringSimple(shape, 3.0);\n\n msdfgen::Bitmap msdf(32, 32);\n msdfgen::SDFTransformation t(\n msdfgen::Projection(msdfgen::Vector2(1.0), msdfgen::Vector2(0.0)),\n msdfgen::Range(4.0));\n msdfgen::generateMSDF(msdf, shape, t);\n\n // Median of the three channels is the reconstructed signed distance.\n auto median_at = [&](int x, int y) {\n const float* px = msdf(x, y);\n return msdfgen::median(px[0], px[1], px[2]);\n };\n\n // Centre of the square is inside → positive; a corner of the bitmap is\n // far outside → negative. msdfgen encodes 0.5 as the edge.\n const float inside = median_at(16, 16);\n const float outside = median_at(0, 0);\n check(inside > 0.5f, std::format(\"centre should read inside, got {}\", inside));\n check(outside < 0.5f, std::format(\"corner should read outside, got {}\", outside));\n\n // Multi-channel: at least one pixel must have channels that differ,\n // otherwise edge colouring never ran and this is a plain SDF.\n bool multi_channel = false;\n for (int y = 0; y < 32 && !multi_channel; ++y) {\n for (int x = 0; x < 32; ++x) {\n const float* px = msdf(x, y);\n if (px[0] != px[1] || px[1] != px[2]) {\n multi_channel = true;\n break;\n }\n }\n }\n check(multi_channel, \"all three channels identical — edge colouring did not run\");\n\n // ext/import-font is compiled in: initializing FreeType through msdfgen\n // proves the FreeType-backed half of the package is present, and that the\n // transitive compat.freetype dependency reached the link line.\n msdfgen::FreetypeHandle* ft = msdfgen::initializeFreetype();\n check(ft != nullptr, \"msdfgen::initializeFreetype returned null\");\n if (ft) {\n msdfgen::deinitializeFreetype(ft);\n }\n\n std::println(\"compat.msdfgen smoke test: {} (inside={:.3f} outside={:.3f})\",\n ok ? \"ok\" : \"FAILED\", inside, outside);\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "mtdev", + "text": "[package]\nname = \"mtdev-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\nmtdev = \"1.1.7\"\n", + "files": [ + { + "path": "tests/examples/mtdev/tests/mtdev.cpp", + "text": "// compat.mtdev — behavioral test, no input device required.\n//\n// mtdev translates kernel multitouch protocol A into protocol B. The test\n// exercises the translator's own state machine rather than a device: the\n// interesting failure is a package that links but whose slot machinery was\n// compiled out, and that shows up here rather than on a touchscreen.\n\n#ifdef __linux__\n\n#include \n#include \n#include \n\n#include \n#include \n\nnamespace {\nint failures = 0;\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n} // namespace\n\nint main()\n{\n // ── 1. A device object can be created and configured ─────────────────\n // mtdev_new/mtdev_init are the plumbing entry points: they set up the\n // slot state without needing an fd, which is what makes this testable.\n mtdev *dev = mtdev_new();\n check(dev != nullptr, \"mtdev_new allocates a translator\");\n if (dev == nullptr) {\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n check(mtdev_init(dev) == 0, \"mtdev_init sets up the slot state machine\");\n\n // ── 2. It reports what it can translate ──────────────────────────────\n // ABS_MT_POSITION_X is the axis every protocol-A device carries; asking\n // about it exercises the capability table the translator is built from.\n mtdev_set_mt_event(dev, ABS_MT_POSITION_X, 1);\n check(mtdev_has_mt_event(dev, ABS_MT_POSITION_X) != 0,\n \"a multitouch axis can be declared and read back\");\n mtdev_set_mt_event(dev, ABS_MT_POSITION_X, 0);\n check(mtdev_has_mt_event(dev, ABS_MT_POSITION_X) == 0,\n \"…and cleared again\");\n\n // ── 3. With nothing fed in, nothing comes out ────────────────────────\n // mtdev_empty is the drain check libinput calls in its read loop. On a\n // fresh translator it must be true — a package whose buffers were\n // compiled wrong tends to report data that was never written.\n check(mtdev_empty(dev) != 0, \"a fresh translator has no pending events\");\n\n mtdev_close(dev);\n mtdev_delete(dev);\n check(true, \"mtdev_close and mtdev_delete complete\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "muduo", + "text": "# compat.muduo test project. The check exercises a public network buffer API.\n[package]\nname = \"muduo-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nmuduo = \"2.0.3\"\n", + "files": [ + { + "path": "tests/examples/muduo/tests/buffer.cpp", + "text": "#ifdef __linux__\n#include \n\n#include \n\nint main() {\n muduo::net::Buffer buffer;\n const char payload[] = \"muduo\";\n buffer.append(payload, sizeof(payload) - 1);\n if (buffer.readableBytes() != sizeof(payload) - 1) return 1;\n if (std::memcmp(buffer.peek(), payload, sizeof(payload) - 1) != 0) return 1;\n buffer.retrieveAll();\n return buffer.readableBytes() == 0 ? 0 : 1;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "mysql-connector-cpp", + "text": "[package]\nname = \"mysql-connector-cpp-tests\"\nversion = \"0.1.0\"\n\n# Connector/C++ 同时构建 X DevAPI 和 JDBC,并依赖源码构建的\n# compat.libmysqlclient/compat.openssl,因此目前只覆盖 Linux/macOS。\n[target.'cfg(linux)'.dependencies.compat]\nmysql-connector-cpp = \"26.7.0\"\n\n[target.'cfg(linux)'.build]\ncxxflags = [\"-DHAVE_MYSQL_CONNECTOR_CPP=1\"]\n\n[target.'cfg(macos)'.dependencies.compat]\nmysql-connector-cpp = \"26.7.0\"\n\n[target.'cfg(macos)'.build]\ncxxflags = [\"-DHAVE_MYSQL_CONNECTOR_CPP=1\"]\n", + "files": [ + { + "path": "tests/examples/mysql-connector-cpp/tests/connector.cpp", + "text": "#ifdef HAVE_MYSQL_CONNECTOR_CPP\n#include \n#include \n\n#include \n\nint main() {\n // 同一个 Connector/C++ 包应同时提供 X DevAPI 和 JDBC。\n mysqlx::Value value(std::string(\"mcpp\"));\n if (value.get() != \"mcpp\") return 1;\n\n sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();\n if (driver == nullptr) return 2;\n if (driver->getMajorVersion() != 26) return 3;\n if (driver->getMinorVersion() != 7) return 4;\n return driver->getPatchVersion() == 0 ? 0 : 5;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "nanodbc", + "text": "# nanodbc test project: assert the ODBC driver manager round-trips through the\n# wrapper. CI has no database and no ODBC driver, but it has the driver manager\n# (unixODBC on linux), which is exactly the layer nanodbc wraps -- so the\n# behavioral assertion drives the ERROR path: connecting to a DSN that does\n# not exist must throw nanodbc::database_error carrying the manager's own\n# diagnostics.\n#\n# Gated to linux: windows (-lodbc32, Windows SDK) and macOS (-liodbc) are\n# declared in the descriptor for consumers but are not exercised by this\n# index's CI -- the linux leg is the one where the system driver manager is\n# guaranteed present.\n[package]\nname = \"nanodbc-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nnanodbc = \"2.14.0\"\n\n[target.'cfg(linux)'.build]\ncxxflags = [\"-DHAVE_NANODBC=1\"]\n", + "files": [ + { + "path": "tests/examples/nanodbc/tests/error_path.cpp", + "text": "// compat.nanodbc — connect to a DSN that does not exist and assert nanodbc\n// reports the driver manager's own diagnostics as a nanodbc::database_error.\n//\n// Deliberately DATABASE-FREE. A CI runner has no database server and no ODBC\n// driver, so a successful connect asserts nothing that can hold everywhere.\n// What IS guaranteed is the driver manager (unixODBC), which is the layer\n// nanodbc wraps: the failed connect still walks handle allocation, the DSN\n// lookup and the diagnostic formatting, and every one of those symbols comes\n// from the compiled nanodbc.cpp — a half-linked package fails here.\n#ifdef HAVE_NANODBC\n\n#include \nimport std;\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n bool thrown = false;\n try {\n // A DSN that cannot exist anywhere; SQL_DRIVER_NOPROMPT keeps the DM\n // from trying to raise a UI prompt on a headless runner.\n nanodbc::connection conn(\n NANODBC_TEXT(\"DSN=mcpp-definitely-missing\"), NANODBC_TEXT(\"\"),\n NANODBC_TEXT(\"\"), 1 /* timeout */);\n check(false, \"connecting to a nonexistent DSN must not succeed\");\n } catch (const nanodbc::database_error& e) {\n thrown = true;\n std::println(\"caught database_error: state={} what={}\", e.state(),\n e.what());\n // IM002, \"[Driver Manager]Data source name not found\", is the proof\n // the diagnostics came out of the real driver manager, not a stub.\n //\n // nanodbc 2.14.0 itself drops the LAST state character (recent_error\n // in nanodbc.cpp loops to size(sql_state) - 1), so state() reads\n // \"IM00\" where the DM said \"IM002\". Frozen upstream, so assert the\n // prefix relationship rather than equality.\n check(!e.state().empty() && std::string_view(\"IM002\").starts_with(e.state()),\n \"SQL state must be a non-empty prefix of IM002\");\n check(std::string_view(e.what()).find(\"Data source name not found\") !=\n std::string_view::npos,\n \"what() must carry the driver manager's own message\");\n } catch (const std::exception& e) {\n check(false, std::format(\"wrong exception type: {}\", e.what()));\n }\n check(thrown, \"nanodbc::database_error must be thrown for a bogus DSN\");\n\n if (ok) std::println(\"nanodbc error-path test passed\");\n return ok ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; } // non-linux: dependency is cfg-gated out, nothing to test\n\n#endif\n" + } + ] + }, + { + "project": "nanosvg", + "text": "# nanosvg test project: parse a real SVG document and rasterize it, asserting\n# the geometry and the pixels -- which also proves the package's generated\n# implementation TU is linked (both nanosvg and nanosvgrast).\n[package]\nname = \"nanosvg-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nnanosvg = \"2026.07.09\"\n", + "files": [ + { + "path": "tests/examples/nanosvg/tests/parse.cpp", + "text": "// compat.nanosvg — parse a real SVG document and rasterize it.\n//\n// This asserts three separate things the package must get right:\n// 1. `` / `` resolve (include_dirs points at src/),\n// 2. the parser's output is real geometry, not an empty image, and\n// 3. the generated implementation TU is actually linked -- BOTH halves of it.\n// nsvgParse comes from nanosvg.h's implementation and nsvgRasterize from\n// nanosvgrast.h's, so if the package only instantiated one of the two this\n// test fails at link time rather than silently passing.\n#include \n#include \nimport std;\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n // A 100x100 canvas holding one opaque red rectangle covering the middle\n // half. nsvgParse mutates its input, so hand it a writable copy.\n std::string svg =\n R\"()\"\n R\"()\"\n R\"()\";\n\n NSVGimage* image = nsvgParse(svg.data(), \"px\", 96.0f);\n check(image != nullptr, \"nsvgParse returned an image\");\n if (!image) return 1;\n\n check(image->width == 100.0f && image->height == 100.0f, \"canvas dimensions\");\n\n int shapes = 0, paths = 0;\n for (NSVGshape* sh = image->shapes; sh != nullptr; sh = sh->next) {\n ++shapes;\n for (NSVGpath* p = sh->paths; p != nullptr; p = p->next) ++paths;\n }\n check(shapes == 1, \"one shape parsed\");\n check(paths >= 1, \"the shape has at least one path\");\n\n // The rect's bounds must be the ones the document declared.\n if (image->shapes) {\n const float* b = image->shapes->bounds;\n check(std::abs(b[0] - 25.0f) < 0.5f && std::abs(b[1] - 25.0f) < 0.5f &&\n std::abs(b[2] - 75.0f) < 0.5f && std::abs(b[3] - 75.0f) < 0.5f,\n \"shape bounds match the declared rect\");\n // fill colour is packed ABGR by nanosvg\n check((image->shapes->fill.color & 0x00ffffffu) == 0x000000ffu,\n \"fill colour is red\");\n }\n\n // ---- rasterize -------------------------------------------------------\n NSVGrasterizer* rast = nsvgCreateRasterizer();\n check(rast != nullptr, \"rasterizer created\");\n if (rast) {\n constexpr int W = 100, H = 100;\n std::vector px(static_cast(W) * H * 4, 0);\n nsvgRasterize(rast, image, 0.0f, 0.0f, 1.0f, px.data(), W, H, W * 4);\n\n auto at = [&](int x, int y) -> const unsigned char* {\n return px.data() + (static_cast(y) * W + x) * 4;\n };\n // Inside the rect: opaque red. Outside: untouched (alpha 0).\n const unsigned char* in = at(50, 50);\n const unsigned char* out = at(5, 5);\n check(in[3] > 200, \"centre pixel is opaque\");\n check(in[0] > 200 && in[1] < 60 && in[2] < 60, \"centre pixel is red\");\n check(out[3] == 0, \"corner pixel is untouched\");\n\n std::size_t covered = 0;\n for (std::size_t i = 3; i < px.size(); i += 4) if (px[i] > 128) ++covered;\n // 50x50 of 100x100 = 2500 px, allow for antialiased edges.\n check(covered > 2300 && covered < 2700, \"covered area matches a 50x50 rect\");\n\n nsvgDeleteRasterizer(rast);\n }\n\n nsvgDelete(image);\n\n if (ok) std::println(\"nanosvg OK\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "nlohmann.json", + "text": "# nlohmann/json test project: consumes the C++23 module `nlohmann.json` and\n# asserts round-trip + ordered_json under `mcpp test`.\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `nlohmann`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nnlohmann = { path = \"../../..\" }\n\n[package]\nname = \"nlohmann-json-tests\"\nversion = \"0.1.0\"\n\n[dependencies.nlohmann]\njson = \"3.12.0\"\n", + "files": [ + { + "path": "tests/examples/nlohmann.json/tests/roundtrip.cpp", + "text": "// Behavioral test: build → dump → parse round-trip + the ordered_json variant.\nimport std;\nimport nlohmann.json;\nusing namespace nlohmann::literals;\n\nint main() {\n nlohmann::json j = { {\"answer\", 42}, {\"list\", {1, 2, 3}} };\n nlohmann::json parsed = nlohmann::json::parse(j.dump());\n auto lit = R\"({\"k\":true})\"_json;\n nlohmann::ordered_json oj; oj[\"z\"] = 1; oj[\"a\"] = 2;\n bool ok = parsed[\"answer\"] == 42 && parsed[\"list\"][2] == 3\n && lit[\"k\"] == true && oj.dump() == R\"({\"z\":1,\"a\":2})\";\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "openblas", + "text": "# OpenBLAS test project (L1-cfg consumer): openblas is a Windows-only runtime dep\n# (import lib + DLL); on linux/macos the test is a no-op. Exercises mcpp's\n# platform-conditional [target.'cfg(...)'] deps + flags + windows-runtime-DLL\n# deployment. Resolves the openblas recipe from this repo's own index.\n[package]\nname = \"openblas-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(windows)'.dependencies.compat]\nopenblas = \"0.3.33\"\n[target.'cfg(windows)'.build]\ncxxflags = [\"-DHAVE_OPENBLAS=1\"]\n", + "files": [ + { + "path": "tests/examples/openblas/tests/dgemm.cpp", + "text": "// On Windows the cfg-gated openblas dep + HAVE_OPENBLAS are active: cblas_dgemm\n// asserting [19 22; 43 50]. Elsewhere a no-op (openblas is Windows-only).\n#ifdef HAVE_OPENBLAS\n#include \nint main() {\n const double A[4] = {1, 2, 3, 4};\n const double B[4] = {5, 6, 7, 8};\n double C[4] = {0, 0, 0, 0};\n cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, 2, 2, 2, 1.0, A, 2, B, 2, 0.0, C, 2);\n const double expected[4] = {19, 22, 43, 50};\n for (int i = 0; i < 4; ++i) if (C[i] != expected[i]) return 10 + i;\n return 0;\n}\n#else\nint main() { return 0; } // openblas is Windows-only; no-op elsewhere\n#endif\n" + } + ] + }, + { + "project": "opencl", + "text": "[package]\nname = \"opencl-tests\"\nversion = \"0.1.0\"\n\n# The loader on every platform it has one, and on Linux the runtime adapter it\n# depends on. The adapter is what lets the loader's dlopen of a host ICD resolve\n# from inside mcpp's own C library; on a runner with no driver it is empty and\n# the loader reports zero platforms.\n#\n# WINDOWS NEEDS NO ADAPTER AND IS NOT A SKIP ANY MORE. The loader reads the\n# registry, DXGK and app packages there, and each entry names a driver DLL by\n# absolute path that the SYSTEM loader resolves. `compat.opencl` gained its\n# windows section for that reason; the test below is the same source on all\n# three platforms.\n[dependencies.compat]\nopencl = \"2026.05.29\"\n\n[build]\ncxxflags = [\"-DHAVE_OPENCL_LOADER=1\"]\n", + "files": [ + { + "path": "tests/examples/opencl/tests/platforms.cpp", + "text": "// Behavioural test: compat.opencl builds a loader a consumer can link, and the\n// loader can enumerate whatever the machine advertises.\n//\n// Zero platforms is a pass. Every CI runner in this repository has no OpenCL\n// driver, and CL_PLATFORM_NOT_FOUND_KHR is the loader's own answer for that\n// case; it proves the trampolines are linked and the ICD scan ran. A machine\n// with a driver, or with xim:pocl announced through OCL_ICD_FILENAMES, lists\n// its platforms and devices; the test prints them so the log says which case\n// it measured.\n//\n// Classic includes rather than `import std;`: `CL/cl_platform.h` includes\n// ``, which pulls `` into the global module before the\n// std module is imported, and GCC then reports conflicting language linkage\n// for the declarations both provide.\n#include \n#if !defined(HAVE_OPENCL_LOADER)\n// Reached only if a consumer builds this file without the loader on the line.\n// It used to be the Windows path, which had no loader to depend on; the\n// package has one now and the manifest declares it on every platform.\nint main() {\n std::printf(\"compat.opencl: skipped (built without the loader)\\n\");\n return 0;\n}\n#else\n#define CL_TARGET_OPENCL_VERSION 300\n#include \n#include \n#include \n\nint main() {\n cl_uint n = 0;\n const cl_int rc = clGetPlatformIDs(0, nullptr, &n);\n if (rc == CL_PLATFORM_NOT_FOUND_KHR || (rc == CL_SUCCESS && n == 0)) {\n std::printf(\"compat.opencl: loader linked, no ICD advertised on this machine\\n\");\n return 0;\n }\n if (rc != CL_SUCCESS) {\n std::printf(\"clGetPlatformIDs failed: %d\\n\", static_cast(rc));\n return 1;\n }\n std::vector platforms(n);\n if (clGetPlatformIDs(n, platforms.data(), nullptr) != CL_SUCCESS) {\n std::printf(\"clGetPlatformIDs (fill) failed\\n\");\n return 2;\n }\n for (auto p : platforms) {\n char name[256] = {};\n clGetPlatformInfo(p, CL_PLATFORM_NAME, sizeof name, name, nullptr);\n cl_uint devices = 0;\n clGetDeviceIDs(p, CL_DEVICE_TYPE_ALL, 0, nullptr, &devices);\n std::printf(\"platform: %s (%u device(s))\\n\", name, devices);\n std::vector ids(devices);\n if (devices > 0 && clGetDeviceIDs(p, CL_DEVICE_TYPE_ALL, devices, ids.data(), nullptr) == CL_SUCCESS) {\n for (auto d : ids) {\n char dname[256] = {};\n clGetDeviceInfo(d, CL_DEVICE_NAME, sizeof dname, dname, nullptr);\n std::printf(\" device: %s\\n\", dname);\n }\n }\n }\n return 0;\n}\n#endif\n" + } + ] + }, + { + "project": "opencv-module-dnn", + "text": "# Public `opencv` module package `dnn` FEATURE test: `opencv = { features =\n# [\"dnn\"] }` builds the underlying dnn sources and compiles opencv-m's\n# opencv.dnn interface, so `import opencv.dnn;` is available. All three\n# platforms (mlas on linux/macOS, built-in fast_gemm on windows).\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `opencv`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nopencv = { path = \"../../..\" }\n\n[package]\nname = \"opencv-module-dnn-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.opencv]\nopencv = { version = \"5.0.0\", features = [\"dnn\"] }\n\n# macOS: `import opencv.dnn;` on macOS-arm64 (per-OS NEON dnn payload).\n[target.'cfg(macos)'.dependencies.opencv]\nopencv = { version = \"5.0.0\", features = [\"dnn\"] }\n\n# windows: `import opencv.dnn;` on windows-x86_64 (fast_gemm backend).\n[target.'cfg(windows)'.dependencies.opencv]\nopencv = { version = \"5.0.0\", features = [\"dnn\"] }\n\n\n", + "files": [ + { + "path": "tests/examples/opencv-module-dnn/tests/opencv_dnn_iface.cpp", + "text": "// The opencv.dnn MODULE interface (not textual headers) must expose the dnn\n// surface when opencv is pulled with features=[\"dnn\"] — proves the feature\n// compiled src/dnn.cppm and built the underlying dnn sources.\n// Not named dnn.cpp (would collide with the dep's\n// modules/dnn/src/dnn.cpp — #240 family). Import-only (import std + the opencv\n// modules): no textual headers, matching the ffmpeg-module member convention.\n#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)\nimport std;\nimport opencv.cv;\nimport opencv.dnn;\n\nint main() {\n cv::Mat img(32, 32, cv::CV_8UC3, cv::Scalar(10, 20, 30));\n cv::Mat blob = cv::dnn::blobFromImage(img, 1.0 / 255.0, cv::Size(16, 16),\n cv::Scalar(), true, false);\n if (blob.dims != 4 || blob.size[0] != 1 || blob.size[1] != 3\n || blob.size[2] != 16 || blob.size[3] != 16) return 1;\n float v = blob.ptr(0)[0];\n if (v < 0.117f || v > 0.118f) return 2; // 30/255, channels swapped\n cv::dnn::Net net;\n if (!net.empty()) return 3;\n std::println(\"opencv.dnn module ok: blobFromImage 1x3x16x16 first={}\", v);\n return 0;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "opencv-module-unifont", + "text": "# Public `opencv` module package `unifont` FEATURE test: `opencv = { features =\n# [\"unifont\"] }` embeds the CJK font (no new module surface), so FontFace(\"uni\")\n# renders CJK through `import opencv.cv;`. Exercised on linux and macOS.\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `opencv`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nopencv = { path = \"../../..\" }\n\n[package]\nname = \"opencv-module-unifont-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.opencv]\nopencv = { version = \"5.0.0\", features = [\"unifont\"] }\n\n[target.'cfg(macos)'.dependencies.opencv]\nopencv = { version = \"5.0.0\", features = [\"unifont\"] }\n", + "files": [ + { + "path": "tests/examples/opencv-module-unifont/tests/opencv_unifont_iface.cpp", + "text": "// The \"uni\" FontFace only exists when opencv's unifont feature embedded the\n// font — CJK rendering through the module layer must ink. Import-only (import std + opencv.cv): no textual\n// headers, matching the ffmpeg-module member convention.\n#if defined(__linux__) || defined(__APPLE__)\nimport std;\nimport opencv.cv;\n\nint main() {\n cv::FontFace uni(\"uni\");\n cv::Mat img(64, 256, cv::CV_8UC1, cv::Scalar(0));\n cv::putText(img, \"中文字体\", cv::Point(8, 44), cv::Scalar(255), uni, 28);\n int ink = cv::countNonZero(img);\n if (ink < 100) { std::println(\"opencv.unifont: CJK ink {} too low\", ink); return 1; }\n std::println(\"opencv.unifont module ok: CJK putText ink={}\", ink);\n return 0;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "opencv-module", + "text": "# Public `opencv` C++23-module package test (QUALIFIED consumption from the\n# `opencv` namespace — `mcpplibs:opencv` was frozen by #165 and deleted once\n# every consumer had moved, so the bare name resolves to nothing at all now).\n# Since opencv 0.0.7 the package is single-repo: the OpenCV sources are\n# vendored in its own release tarball, so this member exercises the whole\n# source build on all three platforms.\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `opencv`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\nopencv = { path = \"../../..\" }\n\n[package]\nname = \"opencv-module-tests\"\nversion = \"0.1.0\"\n\n# The package's only remaining transitive dep is compat.ffmpeg (videoio\n# backend), which resolves from the GLOBAL published compat index: this\n# member redirects `default`, and a member-level [indices] replaces the\n# root's `compat` rather than adding to it. The compat descriptor itself is\n# pre-merge-validated by its own dedicated member.\n[target.'cfg(linux)'.dependencies.opencv]\nopencv = \"5.0.0\"\n\n[target.'cfg(macos)'.dependencies.opencv]\nopencv = \"5.0.0\"\n\n# windows: import opencv.cv on windows-x86_64 (clang-cl; module tarball OS-neutral).\n[target.'cfg(windows)'.dependencies.opencv]\nopencv = \"5.0.0\"\n", + "files": [ + { + "path": "tests/examples/opencv-module/tests/opencv_module.cpp", + "text": "// Public opencv module package end-to-end assertion: import-only consumption\n// (`import opencv.cv;`), pinned version, Mat/imgproc ops (incl. the\n// replacement operator surface: Size comparison via != crosses the module\n// boundary — the v0.0.1 regression scenario), PNG+JPEG codec roundtrips,\n// videoio registry.\n#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)\nimport std;\nimport opencv.cv;\n\nint main() {\n if (cv::getVersionString() != \"5.0.0\") return 1;\n\n cv::Mat img(64, 64, cv::CV_8UC3, cv::Scalar(30, 60, 90));\n cv::circle(img, {32, 32}, 20, {255, 255, 255}, -1);\n cv::Mat big, gray;\n cv::resize(img, big, {128, 128}, 0, 0, cv::INTER_CUBIC);\n cv::cvtColor(big, gray, cv::COLOR_BGR2GRAY);\n if (gray.size() != cv::Size(128, 128)) return 2;\n\n for (const char* ext : { \".png\", \".jpg\" }) {\n std::vector buf;\n if (!cv::imencode(ext, big, buf)) return 3;\n cv::Mat back = cv::imdecode(buf, cv::IMREAD_COLOR);\n if (back.empty() || back.size() != big.size()) return 4;\n }\n\n if (cv::videoio_registry::getBackends().empty()) return 5;\n std::println(\"OpenCV {} module package ok\", cv::getVersionString());\n return 0;\n}\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "openssl", + "text": "# compat.openssl test project — the dedicated member that validates the\n# from-source OpenSSL build (static libssl.a + libcrypto.a + headers on\n# linux/macOS; libssl.lib + libcrypto.lib on Windows).\n#\n# It inherits the workspace-root `compat` redirect, so the descriptor in THIS\n# checkout is what gets built and linked. tests/examples/asio-ssl covers the\n# other direction — the same package pulled in as asio's `ssl` feature dep —\n# and this member is what isolates a failure to openssl itself rather than to\n# the feature wiring around it.\n#\n# All three platforms: linux/macOS build via perl Configure + GNU Make, windows\n# via VC-WIN64A + nmake (see _install_windows in the descriptor). The member\n# declares the dependency and HAVE_OPENSSL on every platform, so the test is a\n# real test everywhere — not a no-op main() on windows.\n[package]\nname = \"openssl-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nopenssl = \"3.5.1\"\n\n[target.'cfg(linux)'.build]\ncxxflags = [\"-DHAVE_OPENSSL=1\"]\n\n[target.'cfg(macos)'.dependencies.compat]\nopenssl = \"3.5.1\"\n\n[target.'cfg(macos)'.build]\ncxxflags = [\"-DHAVE_OPENSSL=1\"]\n\n[target.'cfg(windows)'.dependencies.compat]\nopenssl = \"3.5.1\"\n\n[target.'cfg(windows)'.build]\ncxxflags = [\"-DHAVE_OPENSSL=1\"]\n", + "files": [ + { + "path": "tests/examples/openssl/tests/tls.cpp", + "text": "// compat.openssl end-to-end: headers resolve, both archives link, and the\n// library initialises far enough to stand up a TLS context and run a digest.\n//\n// Touching BOTH archives is the point: libssl.a (SSL_CTX_new, TLS_method) and\n// libcrypto.a (EVP_*) — libssl.lib / libcrypto.lib on Windows. A link that\n// silently dropped one, or picked up a host libssl.so instead of the package's\n// own static build, fails here.\n//\n// HAVE_OPENSSL comes from this project's own cfg-gated cxxflags. The package\n// is supported on all three platforms now (linux/macOS: perl Configure + GNU\n// Make; windows: VC-WIN64A + nmake), so the real test runs everywhere.\n#ifdef HAVE_OPENSSL\n#include \n#include \n#include \n\n#include \n\nint main() {\n // Built from the 3.5.1 tarball this descriptor pins.\n if (OPENSSL_VERSION_MAJOR != 3 || OPENSSL_VERSION_MINOR != 5) return 1;\n\n // libssl: a usable client context.\n SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());\n if (ctx == nullptr) return 2;\n SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);\n SSL* ssl = SSL_new(ctx);\n if (ssl == nullptr) { SSL_CTX_free(ctx); return 3; }\n SSL_free(ssl);\n SSL_CTX_free(ctx);\n\n // libcrypto: SHA-256 of \"abc\" — first four bytes of the known digest.\n unsigned char digest[EVP_MAX_MD_SIZE] = {};\n unsigned int len = 0;\n EVP_MD_CTX* md = EVP_MD_CTX_new();\n if (md == nullptr) return 4;\n if (EVP_DigestInit_ex(md, EVP_sha256(), nullptr) != 1\n || EVP_DigestUpdate(md, \"abc\", 3) != 1\n || EVP_DigestFinal_ex(md, digest, &len) != 1) {\n EVP_MD_CTX_free(md);\n return 5;\n }\n EVP_MD_CTX_free(md);\n if (len != 32) return 6;\n\n const unsigned char expected[4] = {0xba, 0x78, 0x16, 0xbf};\n if (std::memcmp(digest, expected, sizeof expected) != 0) return 7;\n return 0;\n}\n#else\nint main() { return 0; } // no HAVE_OPENSSL — fallback that always passes\n#endif\n" + } + ] + }, + { + "project": "pango", + "text": "# gnome.pango — international text layout, without a font backend.\n#\n# ⚠️ NAMES pango ALONE. gio, gobject, glib, harfbuzz and fribidi all arrive\n# transitively; gio in particular is a workspace PATH dependency of pango, and\n# naming it here as well is `requested as both a version dep and a path dep`.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"pango-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\npango = \"1.56.1\"\n", + "files": [ + { + "path": "tests/examples/pango/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. The test\n// next to this file takes the header route; this one takes the module, so both\n// doors are tested rather than assumed.\n//\n// ⚠️ THE TWO DOORS DO NOT COMPOSE. A TU that imports the module AND textually\n// includes a pango or glib header reaches twice — once through the\n// module's global fragment, once directly — and the same `struct tm` becomes\n// two entities. So a consumer picks ONE, and which one is decided by macros:\n// a module cannot carry them. Code using `PANGO_TYPE_*` or `G_OBJECT` takes\n// the header route; code using the function API imports and includes nothing.\n#ifdef __linux__\n\nimport gnome.pango; // re-exports gnome.gio, and glib/gobject behind it\n\n// ⚠️ NO AND NO . The module's global fragment already saw\n// and through pango.h, and reaching them TEXTUALLY as\n// well makes the same `struct _IO_FILE` two entities:\n//\n// error: conflicting declaration 'struct _IO_FILE'\n//\n// glib's own g_print and strcmp-equivalent come through the module, which is\n// the point: on the module route a consumer includes nothing.\n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n g_print(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n g_print(\"import gnome.pango — %s\\n\\n\", pango_version_string());\n\n check(pango_version() >= 15601, \"pango_version() through the module\");\n check(pango_version_check(1, 56, 0) == nullptr,\n \"pango_version_check — PANGO_BINARY_AGE came from config.h\");\n\n // ⚠️ PANGO_TYPE_ALIGNMENT is a MACRO; the module carries the FUNCTION it\n // wraps. That is the shape of the module route, and why the header route\n // stays supported next door.\n check(pango_alignment_get_type() != 0, \"the generated enum GTypes register\");\n check(pango_font_map_get_type() != 0, \"PangoFontMap registers\");\n\n // An ENUMERATOR, exported by name — GCC makes them visible through an\n // exported typedef and clang does not, so they are listed explicitly.\n PangoDirection d = PANGO_DIRECTION_RTL;\n check(static_cast(d) != 0, \"an enumerator is exported by name\");\n\n // Script itemisation: pure Unicode table work, no fonts, no macros.\n {\n const char *text = \"Hello \\344\\270\\226\\347\\225\\214\";\n PangoScriptIter *it = pango_script_iter_new(text, -1);\n int runs = 0;\n do { ++runs; } while (pango_script_iter_next(it));\n pango_script_iter_free(it);\n check(runs >= 2, \"pango_script_iter splits Latin from Han\");\n }\n\n // Line breaking.\n {\n PangoLogAttr attrs[16] = {};\n pango_get_log_attrs(\"hello world\", -1, -1,\n pango_language_from_string(\"en-US\"), attrs, 16);\n check(attrs[6].is_line_break, \"a line break is allowed before \\\"world\\\"\");\n }\n\n // The markup parser, which reaches glib's GMarkup through the re-export\n // chain — gnome.pango pulls gnome.gio, which re-exports gnome.glib.\n {\n char *text = nullptr;\n const gboolean ok = pango_parse_markup(\"a bold word\", -1, 0,\n nullptr, &text, nullptr, nullptr);\n check(ok && text && g_strcmp0(text, \"a bold word\") == 0,\n \"pango_parse_markup — and g_free below is glib, transitively\");\n g_free(text);\n }\n\n g_print(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n#endif\n" + }, + { + "path": "tests/examples/pango/tests/pango.cpp", + "text": "// pango — international text layout, exercised rather than merely linked.\n//\n// WHAT CAN BE ASSERTED WITHOUT A FONT\n//\n// This member has no font backend: finding and rasterising fonts is pangoft2's\n// job and drawing is pangocairo's. So a layout test here would be asserting\n// that the RUNNER has fonts, which is not what this package is.\n//\n// Everything below is an INTERMEDIATE QUANTITY — something pango computes from\n// data it carries, with no font map involved — and each names a specific part\n// of the build, so a failure says which one broke:\n//\n// the version macros include/pango/pango-features.h, one of two generated\n// headers, and PANGO_BINARY_AGE from config.h — which\n// is not substituted from a template but WRITTEN,\n// because upstream's configure_file has no input\n// the GENERATED enums pango-enum-types.{h,c}, 27 GTypes from a\n// reimplemented glib-mkenums\n// itemisation itemize.c + pango-script.c: splitting a mixed-script\n// string into runs is pure Unicode table work\n// bidi pango-bidi-type.c over compat.fribidi\n// line breaking break.c, the Unicode line-breaking algorithm\n// the attribute list pango-attributes.c + pango-markup.c, the parser\n// GListModel ⭐ what pango was blocked on: PangoFontMap declares\n// G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, …), so the\n// type cannot even register without gio\n\n#ifdef __linux__\n\n// ⚠️ NO extern \"C\" WRAPPER. pango decorates its headers with G_BEGIN_DECLS, so\n// one is redundant — and harmful, because pango.h reaches , which\n// libc++ routes through , which defines TEMPLATES. Inside an\n// extern \"C\" block that is `templates must have C++ linkage`, dozens of times,\n// against a header this file never names.\n#include \n\n// ⚠️ pango.h does NOT pull gio in, even though PangoFontMap implements\n// GListModel — the interface is used in the implementation, not the header.\n// A consumer that wants to treat a font map as a list model includes gio\n// itself, which is legitimate: gnome.gio arrives transitively as a dependency\n// of this package.\n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-60s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\"pango %s\\n\\n\", pango_version_string());\n\n // ── 1. the two generated headers, and config.h ───────────────────────\n check(PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR == 56\n && PANGO_VERSION_MICRO == 1,\n \"pango-features.h reports the version the manifest declares\");\n check(std::strcmp(PANGO_VERSION_STRING, \"1.56.1\") == 0,\n \"…and its @…@ substitution produced the string form\");\n check(pango_version() == PANGO_VERSION,\n \"the runtime version agrees with the compiled-in one\");\n\n // ⚠️ PANGO_BINARY_AGE lives in config.h, which upstream produces with a\n // configure_file that has NO input template — so it is written here rather\n // than substituted, and a wrong value is a wrong ANSWER rather than a\n // build error. pango_version_check consults it.\n check(pango_version_check(1, 56, 0) == nullptr,\n \"pango_version_check accepts 1.56.0 — PANGO_BINARY_AGE is right\");\n check(pango_version_check(1, 99, 0) != nullptr,\n \"…and rejects a version from the future\");\n\n // ── 2. the GENERATED enum types ──────────────────────────────────────\n // These exist only because build.mcpp reproduced glib-mkenums over\n // pango's public headers. Registering one and reading a value back is the\n // only evidence the generator produced REGISTERABLE code rather than a\n // header that happens to compile.\n const GType align = pango_alignment_get_type();\n check(align != 0, \"pango_alignment_get_type() registered a GType\");\n check(align == PANGO_TYPE_ALIGNMENT,\n \"…and the MACRO is spelled the way upstream spells it\");\n\n GEnumClass *ec = static_cast(g_type_class_ref(align));\n GEnumValue *v = ec ? g_enum_get_value(ec, PANGO_ALIGN_CENTER) : nullptr;\n check(v && std::strcmp(v->value_name, \"PANGO_ALIGN_CENTER\") == 0,\n \"…with PANGO_ALIGN_CENTER under its own name\");\n check(v && std::strcmp(v->value_nick, \"center\") == 0,\n \"…and the nick mkenums derives, \\\"center\\\"\");\n if (ec) {\n g_type_class_unref(ec);\n }\n check(pango_wrap_mode_get_type() != 0 && pango_ellipsize_mode_get_type() != 0\n && pango_script_get_type() != 0,\n \"the other generated enum types register too\");\n\n // ── 3. ⭐ GListModel — the gio dependency, at run time ────────────────\n // PangoFontMap's class init declares\n // G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango_font_map_list_model_init)\n // so registering the type at all requires gio's interface to exist. This\n // is the check that the text-layout line is actually joined up.\n const GType fm = pango_font_map_get_type();\n check(fm != 0, \"PangoFontMap registers\");\n check(g_type_is_a(fm, g_list_model_get_type()),\n \"…and it IS a GListModel — the gio gate, at run time\");\n\n // ── 4. itemisation: Unicode script runs, no fonts involved ───────────\n // \"Hello 世界 مرحبا\" is Latin, Han and Arabic. Splitting it is pure table\n // work in pango-script.c.\n {\n const char *text = \"Hello \\344\\270\\226\\347\\225\\214 \\331\\205\\330\\261\\330\\255\\330\\250\\330\\247\";\n PangoScriptIter *it = pango_script_iter_new(text, -1);\n int runs = 0;\n bool saw_han = false, saw_arabic = false;\n do {\n const char *s = nullptr, *e = nullptr;\n PangoScript sc;\n pango_script_iter_get_range(it, &s, &e, &sc);\n ++runs;\n saw_han |= (sc == PANGO_SCRIPT_HAN);\n saw_arabic |= (sc == PANGO_SCRIPT_ARABIC);\n } while (pango_script_iter_next(it));\n pango_script_iter_free(it);\n std::printf(\" script runs: %d\\n\", runs);\n check(runs >= 3, \"pango_script_iter splits a mixed-script string\");\n check(saw_han && saw_arabic, \"…and identifies Han and Arabic by name\");\n }\n\n // ── 5. bidi, which is compat.fribidi doing the work ──────────────────\n // Deprecated since 1.44 and still exported; testing them is testing what\n // the library actually offers.\n G_GNUC_BEGIN_IGNORE_DEPRECATIONS\n check(pango_unichar_direction(0x05D0) == PANGO_DIRECTION_RTL,\n \"Hebrew alef is right-to-left\");\n check(pango_unichar_direction('A') == PANGO_DIRECTION_LTR,\n \"…and 'A' is left-to-right\");\n {\n const char *rtl = \"\\330\\247\\331\\204\\330\\263\\331\\204\\330\\247\\331\\205\"; // \"السلام\"\n check(pango_find_base_dir(rtl, -1) == PANGO_DIRECTION_RTL,\n \"pango_find_base_dir on an Arabic string\");\n }\n G_GNUC_END_IGNORE_DEPRECATIONS\n\n // ── 6. line breaking: break.c over the Unicode algorithm ─────────────\n {\n const char *text = \"hello world\";\n PangoLogAttr attrs[16] = {};\n pango_get_log_attrs(text, -1, -1, pango_language_from_string(\"en-US\"),\n attrs, 16);\n // A break is allowed before \"world\" (offset 6) and not mid-word.\n check(attrs[6].is_line_break, \"a line break is allowed before \\\"world\\\"\");\n check(!attrs[3].is_line_break, \"…and not in the middle of \\\"hello\\\"\");\n check(attrs[5].is_white, \"…and the space is classified as white\");\n }\n\n // ── 7. the markup parser ─────────────────────────────────────────────\n {\n PangoAttrList *attrs = nullptr;\n char *text = nullptr;\n GError *err = nullptr;\n const gboolean ok = pango_parse_markup(\"a bold word\", -1, 0,\n &attrs, &text, nullptr, &err);\n check(ok && text && std::strcmp(text, \"a bold word\") == 0,\n \"pango_parse_markup strips the tags and keeps the text\");\n check(attrs != nullptr, \"…and produces an attribute list\");\n if (attrs) {\n pango_attr_list_unref(attrs);\n }\n g_free(text);\n if (err) {\n g_error_free(err);\n }\n }\n\n // ── 8. language tags, which carry their own sample text tables ───────\n {\n PangoLanguage *ja = pango_language_from_string(\"ja-JP\");\n check(ja != nullptr, \"pango_language_from_string\");\n check(std::strcmp(pango_language_to_string(ja), \"ja-jp\") == 0,\n \"…normalises the tag to lower case\");\n check(pango_language_includes_script(ja, PANGO_SCRIPT_HAN),\n \"…and knows Japanese uses Han\");\n }\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\n#include \n\nint main()\n{\n std::printf(\"pango: this package builds the Linux half; skipping.\\n\");\n return 0;\n}\n\n#endif\n" + } + ] + }, + { + "project": "pangocairo", + "text": "# gnome.pangocairo — the whole text-layout line, ending in pixels.\n#\n# ⭐ Naming this one package pulls seven: pango, pangoft2, gio (and glib,\n# gobject, gmodule behind it), harfbuzz, fribidi, fontconfig, freetype and\n# cairo. The test renders text and counts the ink.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"pangocairo-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\npangocairo = \"1.56.1\"\n", + "files": [ + { + "path": "tests/examples/pangocairo/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. The test\n// next to this file takes the header route; this one takes the module, so both\n// doors are tested rather than assumed.\n//\n// ⚠️ THE TWO DOORS DO NOT COMPOSE. A TU that imports the module AND textually\n// includes a pango or glib header reaches twice — once through the\n// module's global fragment, once directly — and the same `struct tm` becomes\n// two entities. So a consumer picks ONE, and which one is decided by macros:\n// a module cannot carry them. Code using `PANGO_TYPE_*` or `G_OBJECT` takes\n// the header route; code using the function API imports and includes nothing.\n#ifdef __linux__\n\nimport gnome.pangocairo; // re-exports gnome.pango and gnome.pangoft2\n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n g_print(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n g_print(\"import gnome.pangocairo\\n\\n\");\n\n PangoFontMap *map = pango_cairo_font_map_get_default();\n check(map != nullptr, \"pango_cairo_font_map_get_default()\");\n // ⚠️ G_OBJECT_TYPE_NAME is a MACRO and cannot come through a module. The\n // font TYPE is a better check anyway: it asserts the backend directly\n // rather than by a substring of a class name.\n check(map && pango_cairo_font_map_get_font_type(\n reinterpret_cast(map)) == CAIRO_FONT_TYPE_FT,\n \"…and its font type is FreeType — HAVE_CAIRO_FREETYPE took\");\n\n // ⚠️ FAMILIES ARE ENUMERATED BEFORE DRAWING, matching the header-route\n // test next door. When this ran AFTER the draw, the module test drew 0\n // pixels on a runner where the header test drew 116 — same machine, same\n // four families, identical drawing code. The only difference was this\n // call's position, so it is now in the same place in both, and the count\n // is reported so the two are comparable rather than merely both \"ok\".\n int families = -1;\n {\n PangoFontFamily **f = nullptr;\n pango_font_map_list_families(map, &f, &families);\n g_free(f);\n }\n g_print(\" font families visible: %d\\n\", families);\n\n PangoContext *ctx = pango_font_map_create_context(map);\n PangoLayout *layout = pango_layout_new(ctx);\n pango_layout_set_text(layout, \"Hello \\344\\270\\226\\347\\225\\214\", -1);\n PangoFontDescription *desc = pango_font_description_from_string(\"Sans 24\");\n pango_layout_set_font_description(layout, desc);\n check(pango_layout_get_character_count(layout) == 8,\n \"the layout counts 8 characters\");\n\n int w = 0, h = 0;\n pango_layout_get_pixel_size(layout, &w, &h);\n g_print(\" layout pixel size: %dx%d\\n\", w, h);\n\n // ⚠️ THE SURFACE IS CHECKED, and its format is PRINTED. The first version\n // of this test only counted pixels, so when it reported 0 on a runner\n // where the header-route twin reported 116 there was nothing to go on:\n // \"0 pixels\" is a symptom shared by \"nothing was drawn\", \"the surface is\n // in an error state\" and \"the pixel loop is reading the wrong bytes\".\n cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 400, 100);\n g_print(\" surface status=%d format=%d (CAIRO_FORMAT_ARGB32=%d)\\n\",\n (int) cairo_surface_status(surf),\n (int) cairo_image_surface_get_format(surf),\n (int) CAIRO_FORMAT_ARGB32);\n check(cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS,\n \"the ARGB32 surface was created without error\");\n check(cairo_image_surface_get_format(surf) == CAIRO_FORMAT_ARGB32,\n \"…and the format it reports is the one that was asked for\");\n\n cairo_t *cr = cairo_create(surf);\n cairo_set_source_rgb(cr, 0, 0, 0);\n cairo_move_to(cr, 10, 10);\n pango_cairo_show_layout(cr, layout);\n cairo_destroy(cr);\n\n cairo_surface_flush(surf);\n const unsigned char *d = cairo_image_surface_get_data(surf);\n check(d != nullptr, \"the surface has readable pixel data\");\n const int stride = cairo_image_surface_get_stride(surf);\n long drawn = 0;\n for (int y = 0; d && y < 100; ++y)\n for (int x = 0; x < 400; ++x)\n if (d[(long)y * stride + 4 * x + 3] != 0) ++drawn;\n g_print(\" non-transparent pixels: %ld\\n\", drawn);\n\n // ⚠️ Only assertable when the runner HAS fonts — see the header-route test\n // next door for why 0 families is a property of the machine.\n if (families > 0)\n // `> 0`, not a tuned number — see the header-route test next door for\n // why a threshold here is an assertion about the runner's fonts.\n check(drawn > 0, \"pango_cairo_show_layout put ink on the surface\");\n else\n g_print(\" ⚠️ 0 font families: the rendering assertion was NOT run.\\n\");\n\n cairo_surface_destroy(surf);\n pango_font_description_free(desc);\n g_object_unref(layout);\n g_object_unref(ctx);\n\n g_print(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n#endif\n" + }, + { + "path": "tests/examples/pangocairo/tests/pangocairo.cpp", + "text": "// pangocairo — the whole text-layout line, end to end.\n//\n// ⭐ THIS IS THE ONE TEST IN THE STACK THAT PRODUCES PIXELS, and it is worth\n// stating what that costs to reach:\n//\n// gnome.pango itemisation, bidi, line breaking\n// gnome.pangoft2 fontconfig picks the file, FreeType rasterises\n// gnome.gio PangoFontMap is a GListModel\n// compat.harfbuzz shaping\n// compat.fribidi the bidi algorithm\n// freedesktop.cairo the surface the glyphs land on\n//\n// Seven packages, and a blank image means one of them is not doing its job.\n//\n// ⚠️ IT DEGRADES HONESTLY WHEN THERE ARE NO FONTS. `freedesktop.fontconfig`\n// compiles its runtime paths EMPTY on purpose, so a runner with no\n// FONTCONFIG_FILE and no /usr/share/fonts legitimately finds zero families —\n// and then there is nothing to draw and nothing to assert. That case is\n// REPORTED rather than passed over in silence, because \"0 families, so we\n// skipped the only real check\" and \"the text rendered\" must not look alike.\n\n#ifdef __linux__\n\n#include \n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-60s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// How many pixels in an ARGB32 surface are not fully transparent.\nlong ink(cairo_surface_t *s)\n{\n cairo_surface_flush(s);\n const unsigned char *d = cairo_image_surface_get_data(s);\n if (d == nullptr) {\n return -1;\n }\n const int w = cairo_image_surface_get_width(s);\n const int h = cairo_image_surface_get_height(s);\n const int stride = cairo_image_surface_get_stride(s);\n long n = 0;\n for (int y = 0; y < h; ++y) {\n const unsigned char *row = d + static_cast(y) * stride;\n for (int x = 0; x < w; ++x) {\n if (row[4 * x + 3] != 0) {\n ++n;\n }\n }\n }\n return n;\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\"pangocairo (pango %s, cairo %s)\\n\\n\",\n pango_version_string(), cairo_version_string());\n\n // ── the types register, and the inheritance is the dependency graph ──\n const GType fm = pango_cairo_font_map_get_type();\n check(fm != 0, \"PangoCairoFontMap registers\");\n\n PangoFontMap *map = pango_cairo_font_map_get_default();\n check(map != nullptr, \"pango_cairo_font_map_get_default()\");\n check(PANGO_IS_CAIRO_FONT_MAP(map), \"…and it is a PangoCairoFontMap\");\n check(g_type_is_a(G_OBJECT_TYPE(map), g_list_model_get_type()),\n \"…and a GListModel, which is gio, inherited through PangoFontMap\");\n\n // ⚠️ THE BACKEND IS THE CHECK THAT pangoft2 IS ACTUALLY WIRED IN. Without\n // HAVE_CAIRO_FREETYPE the fc font map is never registered and the default\n // map is some other implementation — which still builds, still runs, and\n // never finds a font. So the type NAME is read.\n const char *backend = G_OBJECT_TYPE_NAME(map);\n std::printf(\" font map backend: %s\\n\", backend);\n check(backend != nullptr && std::strstr(backend, \"Fc\") != nullptr,\n \"…and it is the FONTCONFIG-backed map — HAVE_CAIRO_FREETYPE took\");\n\n int families = -1;\n {\n PangoFontFamily **f = nullptr;\n pango_font_map_list_families(map, &f, &families);\n g_free(f);\n }\n std::printf(\" font families visible: %d\\n\", families);\n check(families >= 0, \"pango_font_map_list_families answers\");\n\n // ── layout, entirely without a surface ───────────────────────────────\n PangoContext *ctx = pango_font_map_create_context(map);\n check(ctx != nullptr, \"pango_font_map_create_context\");\n\n PangoLayout *layout = pango_layout_new(ctx);\n pango_layout_set_text(layout, \"Hello \\344\\270\\226\\347\\225\\214\", -1);\n PangoFontDescription *desc = pango_font_description_from_string(\"Sans 24\");\n pango_layout_set_font_description(layout, desc);\n\n check(pango_layout_get_character_count(layout) == 8,\n \"the layout counts 8 characters — \\\"Hello \\\" plus two Han\");\n check(pango_layout_get_line_count(layout) == 1, \"…on one line\");\n\n int w = 0, h = 0;\n pango_layout_get_pixel_size(layout, &w, &h);\n std::printf(\" layout pixel size: %dx%d\\n\", w, h);\n\n // ── and now the pixels ───────────────────────────────────────────────\n cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 400, 100);\n check(cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS,\n \"a 400x100 ARGB32 surface\");\n cairo_t *cr = cairo_create(surf);\n cairo_set_source_rgb(cr, 0, 0, 0);\n cairo_move_to(cr, 10, 10);\n pango_cairo_show_layout(cr, layout);\n cairo_destroy(cr);\n\n const long drawn = ink(surf);\n std::printf(\" non-transparent pixels: %ld\\n\", drawn);\n\n // ⚠️ THE GUARD IS THE LAYOUT'S OWN METRICS, NOT THE FAMILY COUNT.\n //\n // `families > 0` looked like enough and is not. A CI runner reported FOUR\n // families and measured this layout at 80x858545 — a nonsense height on a\n // 100px surface, because \"four families\" there means fontconfig has\n // entries but no usable font behind them. The glyphs then land off the\n // surface and the ink count is arbitrary: 0 in one process and 116 in\n // another, from byte-identical drawing code.\n //\n // An earlier version also asserted `drawn > 100`, which passed here (216\n // pixels, 184 families) and failed there (72) — a threshold calibrated to\n // the developer's font set is an assertion about the MACHINE. Ink is ink.\n const bool usable = families > 0 && w > 0 && h > 0 && h <= 100;\n if (usable) {\n // ⭐ THE WHOLE LINE, IN ONE ASSERTION.\n check(drawn > 0,\n \"pango_cairo_show_layout put ink on the surface — seven packages\");\n } else {\n // Not a pass. The machine cannot render, so the only real check could\n // not run, and saying so is the point: \"it was skipped\" and \"it worked\"\n // must not look alike.\n std::printf(\" \\342\\232\\240 no usable font (families=%d, layout %dx%d on a\\n\"\n \" 100px surface): the rendering assertion was NOT run.\\n\"\n \" That is a property of this machine, not this build.\\n\",\n families, w, h);\n check(drawn >= 0, \"the surface is readable (rendering check skipped)\");\n }\n\n // pangocairo-context.c: the resolution and font options a context carries.\n pango_cairo_context_set_resolution(ctx, 96.0);\n check(pango_cairo_context_get_resolution(ctx) == 96.0,\n \"pangocairo-context.c round-trips the resolution\");\n\n cairo_surface_destroy(surf);\n pango_font_description_free(desc);\n g_object_unref(layout);\n g_object_unref(ctx);\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\n#include \nint main() { std::printf(\"pangocairo: Linux only.\\n\"); return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "pangoft2", + "text": "# gnome.pangoft2 — the FreeType/fontconfig backend.\n[indices]\ngnome = { path = \"../../..\" }\n\n[package]\nname = \"pangoft2-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.gnome]\npangoft2 = \"1.56.1\"\n", + "files": [ + { + "path": "tests/examples/pangoft2/tests/module.cpp", + "text": "// ⭐ THE MODULE, WHICH IS WHAT THE NAMESPACE PROMISES.\n//\n// In this index the namespace is the contract: `compat.xxx` is consumed with\n// `#include`, an owner namespace like `gnome.xxx` exposes `import`. The test\n// next to this file takes the header route; this one takes the module, so both\n// doors are tested rather than assumed.\n//\n// ⚠️ THE TWO DOORS DO NOT COMPOSE. A TU that imports the module AND textually\n// includes a pango or glib header reaches twice — once through the\n// module's global fragment, once directly — and the same `struct tm` becomes\n// two entities. So a consumer picks ONE, and which one is decided by macros:\n// a module cannot carry them. Code using `PANGO_TYPE_*` or `G_OBJECT` takes\n// the header route; code using the function API imports and includes nothing.\n#ifdef __linux__\n\nimport gnome.pangoft2; // re-exports gnome.pango, and gio/glib behind it\n\nint main()\n{\n int failures = 0;\n auto check = [&](bool ok, const char *what) {\n g_print(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n };\n\n g_print(\"import gnome.pangoft2\\n\\n\");\n\n const GType ft2 = pango_ft2_font_map_get_type();\n check(ft2 != 0, \"PangoFT2FontMap registers\");\n check(g_type_is_a(ft2, pango_fc_font_map_get_type()),\n \"…derives from PangoFcFontMap — the fontconfig half\");\n check(g_type_is_a(ft2, pango_font_map_get_type()),\n \"…and from PangoFontMap — gnome.pango came through the re-export\");\n\n PangoFontMap *map = pango_ft2_font_map_new();\n check(map != nullptr, \"pango_ft2_font_map_new()\");\n if (map) {\n PangoFontFamily **f = nullptr;\n int n = -1;\n pango_font_map_list_families(map, &f, &n);\n g_print(\" font families visible: %d\\n\", n);\n check(n >= 0, \"list_families answers (0 is valid — see the header test)\");\n g_free(f);\n g_object_unref(map);\n }\n\n // pango-ot-tag.c, pure table work and no fonts.\n check(pango_ot_tag_to_script(pango_ot_tag_from_script(PANGO_SCRIPT_ARABIC))\n == PANGO_SCRIPT_ARABIC,\n \"the OpenType script tag mapping round-trips\");\n\n g_print(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n#include \nint main() { std::printf(\"linux only\\n\"); return 0; }\n#endif\n" + }, + { + "path": "tests/examples/pangoft2/tests/pangoft2.cpp", + "text": "// pangoft2 — the FreeType/fontconfig backend.\n//\n// ⚠️ WHAT THIS DOES *NOT* ASSERT: that any particular font is found. That\n// depends on the RUNNER having /usr/share/fonts and a fontconfig\n// configuration, and `freedesktop.fontconfig` deliberately compiles its\n// runtime paths EMPTY so it does not silently read the developer's fonts. A\n// test that demanded \"DejaVu Sans\" would be checking the machine, not this\n// build.\n//\n// What IS asserted is everything that does not need a font file: that the\n// font map type registers, that it IS a PangoFontMap and a GListModel, that\n// the fontconfig and FreeType halves are actually linked in (by calling into\n// each), and that enumerating families returns a well-formed answer — which on\n// a runner with no fonts is legitimately zero.\n\n#ifdef __linux__\n\n#include \n#include \n// ⚠️ pango-ot.h is NOT reached through pangoft2.h. It is a separate installed\n// header, and its whole body sits behind `#ifndef PANGO_DISABLE_DEPRECATED` —\n// upstream's own comment is \"Deprecated. Use HarfBuzz directly!\". It is still\n// compiled into this member and still part of its ABI, so it is still tested.\n#include \n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-60s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n std::printf(\"pangoft2 (pango %s)\\n\\n\", pango_version_string());\n\n // ── the types register ───────────────────────────────────────────────\n const GType ft2 = pango_ft2_font_map_get_type();\n check(ft2 != 0, \"PangoFT2FontMap registers\");\n check(g_type_is_a(ft2, pango_fc_font_map_get_type()),\n \"…and derives from PangoFcFontMap — the fontconfig half is linked\");\n check(g_type_is_a(ft2, pango_font_map_get_type()),\n \"…and from PangoFontMap — gnome.pango is linked\");\n check(g_type_is_a(ft2, g_list_model_get_type()),\n \"…and it is a GListModel, inherited through PangoFontMap\");\n\n // ── an actual instance, which runs fontconfig's initialisation ───────\n PangoFontMap *map = pango_ft2_font_map_new();\n check(map != nullptr, \"pango_ft2_font_map_new() — FcInit ran without dying\");\n\n if (map != nullptr) {\n // ⚠️ ZERO IS A VALID ANSWER HERE. compat.fontconfig ships empty runtime\n // paths on purpose, so a runner with no FONTCONFIG_FILE finds no\n // fonts. What is asserted is that the call ANSWERS — the out\n // parameters are consistent — not what it answers.\n PangoFontFamily **families = nullptr;\n int n = -1;\n pango_font_map_list_families(map, &families, &n);\n std::printf(\" font families visible: %d\\n\", n);\n check(n >= 0, \"pango_font_map_list_families answers\");\n check(n == 0 || families != nullptr,\n \"…and the array matches the count it reported\");\n g_free(families);\n\n // The resolution the FT2 map carries, which is its own state rather\n // than anything fontconfig had to find.\n pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(map), 96.0, 96.0);\n\n PangoContext *ctx = pango_font_map_create_context(map);\n check(ctx != nullptr, \"…and it can create a PangoContext\");\n if (ctx != nullptr) {\n check(pango_context_get_font_map(ctx) == map,\n \"…which points back at the font map\");\n g_object_unref(ctx);\n }\n g_object_unref(map);\n }\n\n // ── the OpenType tag helpers: pango-ot-tag.c, pure table work ────────\n {\n const PangoOTTag t = pango_ot_tag_from_script(PANGO_SCRIPT_ARABIC);\n // 'arab' — the OpenType script tag, four packed bytes.\n check(t == PANGO_OT_TAG_MAKE('a', 'r', 'a', 'b'),\n \"pango_ot_tag_from_script(ARABIC) is 'arab'\");\n check(pango_ot_tag_to_script(t) == PANGO_SCRIPT_ARABIC,\n \"…and the mapping round-trips\");\n }\n\n std::printf(\"\\n%s\\n\", failures == 0 ? \"all ok\" : \"FAILURES\");\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\n#include \nint main() { std::printf(\"pangoft2: Linux only.\\n\"); return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "pcre2", + "text": "# compat.pcre2 — glib's regex engine, exercised the way glib uses it.\n[indices]\ncompat = { path = \"../../..\" }\n\n[package]\nname = \"pcre2-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\npcre2 = \"10.44\"\n", + "files": [ + { + "path": "tests/examples/pcre2/tests/pcre2.cpp", + "text": "// compat.pcre2 — exercised the way glib's GRegex uses it.\n//\n// WHAT THIS ASSERTS AND WHY EACH ONE IS HERE\n//\n// The failures worth catching in a regex engine are not \"it did not link\".\n// They are the ones where the library builds, links, runs, and gives a subtly\n// wrong answer because a probe macro said something untrue. Each check below\n// names a specific decision in the descriptor:\n//\n// compile + match the sources really were compiled, not just linked\n// \\p{Han} / \\X SUPPORT_UNICODE, i.e. that pcre2_ucd.c's tables are in\n// PCRE2_UTF that UTF-8 mode works, which is all glib ever uses\n// the character tables that pcre2_chartables.c.dist was compiled, and in the\n// C locale — the file the descriptor does NOT let a\n// `pcre2_dftables` run produce from the build machine\n// pcre2_config(JIT) that JIT is OFF and SAYS so, rather than crashing\n// capture groups pcre2_substring_*, which glib's g_match_info_fetch is\n\n#ifdef __linux__\n\n// ⚠️ REQUIRED BEFORE THE HEADER, and pcre2.h says so with an #error if it is\n// missing. The header uses it to choose which of the three code-unit widths to\n// declare, and this package builds the 8-bit one. glib does exactly this in\n// gregex.c.\n#define PCRE2_CODE_UNIT_WIDTH 8\n#include \n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-60s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n// Compile a pattern and report whether it matched, so each case below is one\n// line. `subject` is UTF-8 throughout, which is the only encoding glib uses.\nbool matches(const char *pattern, const char *subject, uint32_t options = 0)\n{\n int err = 0;\n PCRE2_SIZE off = 0;\n pcre2_code *re = pcre2_compile(reinterpret_cast(pattern),\n PCRE2_ZERO_TERMINATED, options, &err, &off, nullptr);\n if (re == nullptr) {\n PCRE2_UCHAR buf[256];\n pcre2_get_error_message(err, buf, sizeof buf);\n std::printf(\" compile failed at %zu: %s\\n\", off, reinterpret_cast(buf));\n return false;\n }\n pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr);\n const int rc = pcre2_match(re, reinterpret_cast(subject),\n std::strlen(subject), 0, 0, md, nullptr);\n pcre2_match_data_free(md);\n pcre2_code_free(re);\n return rc > 0;\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. the version the descriptor names ──────────────────────────────\n char ver[64] = {};\n pcre2_config(PCRE2_CONFIG_VERSION, ver);\n std::printf(\" pcre2 %s\\n\", ver);\n check(std::strncmp(ver, \"10.44\", 5) == 0,\n \"pcre2_config reports 10.44, the version the descriptor names\");\n\n // ── 2. it compiles and matches at all ────────────────────────────────\n check(matches(\"^a+b$\", \"aaab\"), \"compile and match a trivial pattern\");\n check(!matches(\"^a+b$\", \"aaac\"), \"…and decline one that does not match\");\n\n // ── 3. SUPPORT_UNICODE ───────────────────────────────────────────────\n // \\p{...} is a compile-time ERROR without SUPPORT_UNICODE, not a silent\n // mismatch — which is the good case: it fails loudly. The descriptor\n // defines the macro (never as 0; pcre2 tests it with #ifdef).\n check(matches(\"\\\\p{Han}\", \"漢\", PCRE2_UTF | PCRE2_UCP),\n \"\\\\p{Han} matches a Han character — SUPPORT_UNICODE is on\");\n check(!matches(\"\\\\p{Han}\", \"a\", PCRE2_UTF | PCRE2_UCP),\n \"…and does not match a Latin one\");\n\n // \\X is an extended grapheme cluster: \"é\" written as e + U+0301 is ONE\n // grapheme and two code points. This distinguishes real UCD tables from a\n // build that merely accepted the syntax.\n check(matches(\"^\\\\X$\", \"e\\xCC\\x81\", PCRE2_UTF),\n \"\\\\X treats e + combining acute as one grapheme cluster\");\n\n // ── 4. UTF-8 mode ────────────────────────────────────────────────────\n // Without PCRE2_UTF, \".\" matches one BYTE; with it, one code point. A\n // three-byte character therefore distinguishes the two modes exactly.\n check(matches(\"^.$\", \"漢\", PCRE2_UTF), \"with PCRE2_UTF, `.` is one code point\");\n check(!matches(\"^.$\", \"漢\"), \"…and without it, one byte — so a 3-byte char does not match\");\n\n // ── 5. the character tables ──────────────────────────────────────────\n // `\\w` and `[[:alpha:]]` consult pcre2_chartables.c, which upstream would\n // otherwise produce by RUNNING pcre2_dftables against the build machine's\n // locale. The descriptor compiles upstream's C-locale `.dist` instead, so\n // these answers cannot depend on where the package was built.\n //\n // The discriminating case is the second one: in a C locale, byte 0xE9\n // (é in Latin-1) is NOT alphabetic. In an fr_FR.ISO-8859-1 locale it is.\n check(matches(\"^\\\\w+$\", \"abc_123\"), \"\\\\w accepts letters, digits and underscore\");\n check(!matches(\"^[[:alpha:]]$\", \"\\xE9\"),\n \"…and byte 0xE9 is not alphabetic, i.e. the tables are C-locale\");\n\n // ── 6. JIT is off, and says so ───────────────────────────────────────\n // The descriptor compiles pcre2_jit_compile.c without SUPPORT_JIT, so the\n // entry points exist as stubs. A consumer that checks — glib does — takes\n // the interpreter path. The failure this catches is the other one: a build\n // that claims JIT and then has no code behind it.\n uint32_t jit = 0;\n pcre2_config(PCRE2_CONFIG_JIT, &jit);\n std::printf(\" pcre2_config(JIT) = %u\\n\", jit);\n check(jit == 0, \"pcre2_config reports JIT off, matching the descriptor\");\n\n int err = 0;\n PCRE2_SIZE off = 0;\n pcre2_code *re = pcre2_compile(reinterpret_cast(\"(\\\\w+)@(\\\\w+)\"),\n PCRE2_ZERO_TERMINATED, 0, &err, &off, nullptr);\n check(re != nullptr, \"compile a pattern with capture groups\");\n if (re != nullptr) {\n // The JIT stub must FAIL CLEANLY rather than crash or pretend.\n const int jrc = pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);\n std::printf(\" pcre2_jit_compile = %d (PCRE2_ERROR_JIT_BADOPTION is %d)\\n\",\n jrc, PCRE2_ERROR_JIT_BADOPTION);\n check(jrc != 0, \"…and pcre2_jit_compile declines instead of crashing\");\n\n // ── 7. capture groups, which is g_match_info_fetch ───────────────\n pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr);\n const char *subject = \"user@example\";\n const int rc = pcre2_match(re, reinterpret_cast(subject),\n std::strlen(subject), 0, 0, md, nullptr);\n check(rc == 3, \"match yields two capture groups plus the whole match\");\n if (rc == 3) {\n PCRE2_SIZE len = 0;\n PCRE2_UCHAR buf[64] = {};\n len = sizeof buf;\n pcre2_substring_copy_bynumber(md, 1, buf, &len);\n std::printf(\" group 1 = %s\\n\", reinterpret_cast(buf));\n check(std::strcmp(reinterpret_cast(buf), \"user\") == 0,\n \"…and group 1 is the text before the @\");\n }\n pcre2_match_data_free(md);\n pcre2_code_free(re);\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "pixman", + "text": "# pixman test project.\n#\n# Linux-only like its graphics-stack neighbours; the test compiles to a no-op\n# main() elsewhere.\n[package]\nname = \"pixman-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.compat]\npixman = \"0.46.4\"\n", + "files": [ + { + "path": "tests/examples/pixman/tests/pixman.cpp", + "text": "// compat.pixman — behavioral test.\n//\n// The interesting thing about this package is not that it links; it is that\n// pixman dispatches on the CPU at runtime, and this index compiles the SSE2\n// and SSSE3 implementations with per-file `-msse2` / `-mssse3` so that the\n// compiler may emit those instructions THERE and nowhere else. If the flags\n// had been package-wide, code running before pixman's own CPUID check could\n// carry SSSE3 — an illegal instruction on an older machine, from a library\n// whose entire design is to avoid exactly that.\n//\n// So the test does two things a \"does it link\" check would not:\n//\n// * asks pixman which implementation it CHOSE, through a real composite;\n// * composites actual pixels and reads them back, because the SIMD paths\n// are only reached with enough work to be worth dispatching to.\n//\n// No display, no GPU, no threads.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. The library is the version this package says it is ────────────\n std::printf(\" pixman %s (header %d.%d.%d)\\n\", pixman_version_string(),\n PIXMAN_VERSION_MAJOR, PIXMAN_VERSION_MINOR, PIXMAN_VERSION_MICRO);\n check(pixman_version() == PIXMAN_VERSION,\n \"the linked library and the header agree on the version\");\n check(PIXMAN_VERSION_MAJOR == 0 && PIXMAN_VERSION_MINOR == 46,\n \"…and it is 0.46, the version the descriptor pins\");\n\n // ── 2. A real composite, over enough pixels to matter ────────────────\n // 256x256 ARGB: large enough that pixman routes through a fast path\n // rather than the trivial one, which is what exercises the dispatched\n // implementation the SIMD flags were for.\n const int W = 256, H = 256;\n std::vector dst(static_cast(W) * H, 0u);\n\n pixman_color_t colour{};\n colour.red = 0x4000;\n colour.green = 0x8000;\n colour.blue = 0xc000;\n colour.alpha = 0xffff;\n pixman_image_t *src = pixman_image_create_solid_fill(&colour);\n check(src != nullptr, \"pixman_image_create_solid_fill\");\n\n pixman_image_t *out = pixman_image_create_bits(\n PIXMAN_a8r8g8b8, W, H, dst.data(), W * 4);\n check(out != nullptr, \"pixman_image_create_bits 256x256 a8r8g8b8\");\n\n if (src != nullptr && out != nullptr) {\n pixman_image_composite32(PIXMAN_OP_SRC, src, nullptr, out,\n 0, 0, 0, 0, 0, 0, W, H);\n\n // Every pixel must be the colour that was composited. A dispatch that\n // picked a broken implementation shows up here rather than as a crash.\n const std::uint32_t want = 0xff4080c0u;\n std::size_t wrong = 0;\n for (std::uint32_t p : dst) {\n if (p != want) {\n ++wrong;\n }\n }\n std::printf(\" composited %dx%d, first pixel 0x%08x (wanted 0x%08x)\\n\",\n W, H, dst[0], want);\n check(wrong == 0, \"every pixel is the colour that was composited\");\n }\n\n // ── 3. Region arithmetic, the other half of what a compositor uses ───\n // Damage tracking is pixman_region32, and it is pure C — no SIMD, but it\n // is the part a Wayland compositor calls on every frame.\n {\n pixman_region32_t a, b, r;\n pixman_region32_init_rect(&a, 0, 0, 100, 100);\n pixman_region32_init_rect(&b, 50, 50, 100, 100);\n pixman_region32_init(&r);\n check(pixman_region32_union(&r, &a, &b) != 0, \"pixman_region32_union\");\n const pixman_box32_t *e = pixman_region32_extents(&r);\n std::printf(\" union extents: (%d,%d)-(%d,%d)\\n\", e->x1, e->y1, e->x2, e->y2);\n check(e->x1 == 0 && e->y1 == 0 && e->x2 == 150 && e->y2 == 150,\n \"…and the extents are the union of the two rects\");\n pixman_region32_fini(&a);\n pixman_region32_fini(&b);\n pixman_region32_fini(&r);\n }\n\n if (src != nullptr) { pixman_image_unref(src); }\n if (out != nullptr) { pixman_image_unref(out); }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "plf-hive", + "text": "# plf-hive test project: assert the property that makes a hive worth using --\n# element addresses survive erasure of their neighbours -- rather than merely\n# that the header parses.\n#\n# Qualified `compat.plf-hive` so the workspace-root [indices] redirect applies\n# and this tests THIS checkout, not the published remote index.\n[package]\nname = \"plf-hive-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nplf-hive = \"2026.07.31\"\n", + "files": [ + { + "path": "tests/examples/plf-hive/tests/hive.cpp", + "text": "// compat.plf-hive — assert the guarantee that distinguishes a hive from a\n// vector or a deque: an element's ADDRESS stays valid while its neighbours are\n// erased and while new elements are inserted. If the package silently resolved\n// to something else, or the header were stubbed, these checks fail rather than\n// passing vacuously.\n#include \nimport std;\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n plf::hive h;\n\n // Fill, and remember where a few elements physically live.\n std::vector::iterator> its;\n for (int i = 0; i < 200; ++i) its.push_back(h.insert(i));\n check(h.size() == 200, \"size after 200 inserts\");\n\n const int* addr_of_50 = &*its[50];\n const int* addr_of_150 = &*its[150];\n\n // Erase every even element. In a vector this would invalidate everything\n // after the first erasure; in a hive nothing moves.\n std::size_t erased = 0;\n for (int i = 0; i < 200; i += 2) {\n h.erase(its[static_cast(i)]);\n ++erased;\n }\n check(erased == 100, \"erased 100 elements\");\n check(h.size() == 100, \"size after erasures\");\n check(&*its[51] != nullptr, \"surviving iterator still dereferenceable\");\n check(addr_of_50 != nullptr && addr_of_150 != nullptr, \"addresses captured\");\n\n // Odd elements survived, at their original addresses.\n const int* addr_of_51 = &*its[51];\n check(*its[51] == 51, \"surviving element keeps its value\");\n\n // Inserting again reuses the erased slots -- and must not relocate anything.\n for (int i = 0; i < 100; ++i) h.insert(1000 + i);\n check(h.size() == 200, \"size after refill\");\n check(&*its[51] == addr_of_51, \"address stable across erase + insert\");\n check(*its[51] == 51, \"value stable across erase + insert\");\n\n // The surviving originals are exactly the odd numbers.\n long odd_sum = 0;\n long expected = 0;\n for (int v : h) if (v < 1000) odd_sum += v;\n for (int i = 1; i < 200; i += 2) expected += i;\n check(odd_sum == expected, \"surviving elements are exactly the odd ones\");\n\n // Erasing through the range interface and clearing must leave it empty.\n h.clear();\n check(h.empty(), \"empty after clear\");\n check(h.begin() == h.end(), \"begin == end when empty\");\n\n if (ok) std::println(\"plf::hive OK\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "protobuf-gzip", + "text": "# protobuf `gzip` feature member. The sibling tests/examples/protobuf covers\n# the DEFAULT build, where io/gzip_stream.cc compiles to an empty TU and no\n# zlib dependency is pulled at all.\n#\n# Here the feature is requested, which defines HAVE_ZLIB=1 for protobuf's own\n# TUs and pulls compat.zlib as the provider. The long-form dependency spelling\n# is what carries `features`.\n[package]\nname = \"protobuf-gzip-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nprotobuf = { version = \"35.1\", features = [\"gzip\"] }\n", + "files": [ + { + "path": "tests/examples/protobuf-gzip/tests/gzip.cpp", + "text": "// Behavioral test for compat.protobuf's `gzip` feature.\n//\n// io/gzip_stream.cc is wrapped head-to-toe in `#if HAVE_ZLIB`, so with the\n// feature OFF the TU is empty and GzipOutputStream / GzipInputStream have no\n// definitions — this member therefore fails to LINK without the feature,\n// which is the negative half of the verification (see the design doc).\n//\n// With it on: compress a payload, assert the compressed form is actually\n// smaller and is a real gzip stream (magic 0x1f 0x8b), then decompress and\n// assert byte equality.\n#include \n\n#include \"google/protobuf/io/coded_stream.h\"\n#include \"google/protobuf/io/gzip_stream.h\"\n#include \"google/protobuf/io/zero_copy_stream_impl_lite.h\"\n\nnamespace gpb = google::protobuf;\n\nnamespace {\n\nstd::string make_payload() {\n // Highly repetitive, so a working deflate must shrink it a lot. Random\n // data would leave \"compressed\" ~= \"original\" and make the size assertion\n // meaningless.\n std::string s;\n for (int i = 0; i < 2000; ++i) s += \"the quick brown fox jumps over the lazy dog\\n\";\n return s;\n}\n\nbool compress(const std::string& in, std::string* out) {\n gpb::io::StringOutputStream sink(out);\n gpb::io::GzipOutputStream::Options opts;\n opts.format = gpb::io::GzipOutputStream::GZIP;\n gpb::io::GzipOutputStream gz(&sink, opts);\n {\n // GzipOutputStream is a ZeroCopyOutputStream (Next/BackUp); CodedOutputStream\n // is the adapter that turns that into a plain buffer write. It must be\n // destroyed — i.e. flushed back into gz — before gz.Close().\n gpb::io::CodedOutputStream coded(&gz);\n coded.WriteRaw(in.data(), static_cast(in.size()));\n if (coded.HadError()) return false;\n }\n return gz.Close();\n}\n\nbool decompress(const std::string& in, std::string* out) {\n gpb::io::ArrayInputStream source(in.data(), static_cast(in.size()));\n gpb::io::GzipInputStream gz(&source, gpb::io::GzipInputStream::GZIP);\n\n const void* chunk = nullptr;\n int size = 0;\n while (gz.Next(&chunk, &size)) {\n if (size > 0) out->append(static_cast(chunk), static_cast(size));\n }\n return gz.ZlibErrorMessage() == nullptr;\n}\n\n} // namespace\n\nint main() {\n const std::string original = make_payload();\n\n std::string compressed;\n if (!compress(original, &compressed)) return 1;\n\n // A real gzip member starts with the 0x1f 0x8b magic.\n if (compressed.size() < 2) return 1;\n if (static_cast(compressed[0]) != 0x1f) return 1;\n if (static_cast(compressed[1]) != 0x8b) return 1;\n\n // Repetitive input must compress substantially; this also proves deflate\n // ran rather than the stream passing bytes through.\n if (compressed.size() >= original.size() / 10) return 1;\n\n std::string restored;\n if (!decompress(compressed, &restored)) return 1;\n\n return restored == original ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "protobuf-protoc", + "text": "# protobuf `protoc` target member — the compiler, not the runtime.\n#\n# The sibling tests/examples/protobuf covers the runtime and deliberately uses\n# NO generated code. This member is its complement: every line of the message\n# API it touches was emitted, during this build, by a protoc that mcpp built\n# from the SAME descriptor that provides the runtime being linked.\n#\n# That co-provenance is the point. protoc and libprotobuf must agree on the\n# generated-code ABI, and a mismatch there is a runtime failure, not a build\n# error. Here it is not expressible: `tools = [\"protoc\"]` makes the tool's\n# version the dependency's version.\n#\n# WINDOWS: linux + macOS only, matching the descriptor — compat.protobuf does\n# not declare the `protoc` target on windows, because the tool sub-build fails\n# there for reasons not yet diagnosed (see the comment in the descriptor's\n# windows block). The dependency below is therefore per-OS, and tests/codegen.cpp\n# compiles to a visible skip on windows rather than a test that silently proves\n# nothing.\n[package]\nname = \"protobuf-protoc-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# One dependency, two roles: `features` shapes what gets LINKED (the runtime),\n# `tools` asks for a host binary out of the same package. `protoc` pulls in\n# libprotoc's 138 TUs and needs `upb` for the upb generator's runtime — the\n# descriptor's `required_features` states that, so asking for the tool is\n# enough and this manifest does not have to know it.\n[target.'cfg(linux)'.dependencies.compat]\nprotobuf = { version = \"35.1\", tools = [\"protoc\"] }\n\n[target.'cfg(macos)'.dependencies.compat]\nprotobuf = { version = \"35.1\", tools = [\"protoc\"] }\n\n# The runtime alone, so the member still builds and links something real here.\n[target.'cfg(windows)'.dependencies.compat]\nprotobuf = \"35.1\"\n", + "files": [ + { + "path": "tests/examples/protobuf-protoc/tests/codegen.cpp", + "text": "// Behavioral test for compat.protobuf's `protoc` target.\n//\n// Every type used here was emitted DURING THIS BUILD by a protoc that mcpp\n// compiled from the same package that provides the runtime being linked. The\n// test therefore asserts the thing that actually matters about a code\n// generator shipped as a dependency: that its output and the runtime agree.\n//\n// What it drives, and what would break first on a generator/runtime mismatch:\n//\n// nested message + accessors generated_message_reflection.cc\n// enum generated_enum_util.cc\n// repeated message field repeated_ptr_field.cc\n// map field map_field.cc\n// oneof the generated case() discriminator\n// well-known type import timestamp.pb.cc (proves the -I resolved)\n// serialize -> parse round trip wire_format_lite.cc, parse_context.cc\n// reflection over generated msg descriptor.cc against the generated pool\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n\n// compat.protobuf declares no `protoc` target on windows, so nothing was\n// generated and there is nothing to assert. A loud skip beats a test that\n// passes without exercising anything.\n#ifdef _WIN32\nint main() {\n std::puts(\"skipped: compat.protobuf has no protoc target on windows\");\n return 0;\n}\n#else\n\n#include \"google/protobuf/util/time_util.h\"\n\n#include \"inventory.pb.h\"\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char* what) {\n if (!ok) {\n std::fprintf(stderr, \"FAIL: %s\\n\", what);\n ++failures;\n }\n}\n\n} // namespace\n\nint main() {\n GOOGLE_PROTOBUF_VERIFY_VERSION;\n\n inventory::Inventory inv;\n\n inventory::Item* widget = inv.add_items();\n widget->set_sku(\"WIDGET-1\");\n widget->set_quantity(7);\n widget->set_grade(inventory::GRADE_A);\n widget->mutable_dimensions()->set_width(2.5);\n widget->mutable_dimensions()->set_height(4.0);\n widget->set_supplier(\"acme\");\n\n inventory::Item* gizmo = inv.add_items();\n gizmo->set_sku(\"GIZMO-2\");\n gizmo->set_quantity(3);\n gizmo->set_grade(inventory::GRADE_B);\n gizmo->set_warehouse(\"east\");\n\n (*inv.mutable_totals_by_grade())[\"A\"] = 7;\n (*inv.mutable_totals_by_grade())[\"B\"] = 3;\n\n // The well-known type. Reaching this line at all proves protoc resolved\n // the import, and setting it proves timestamp.pb.cc is in the runtime.\n *inv.mutable_updated_at() =\n google::protobuf::util::TimeUtil::SecondsToTimestamp(1735689600);\n\n std::string wire;\n check(inv.SerializeToString(&wire), \"serialize\");\n check(!wire.empty(), \"wire is non-empty\");\n\n inventory::Inventory back;\n check(back.ParseFromString(wire), \"parse\");\n\n check(back.items_size() == 2, \"two items survived the round trip\");\n check(back.items(0).sku() == \"WIDGET-1\", \"item 0 sku\");\n check(back.items(0).quantity() == 7, \"item 0 quantity\");\n check(back.items(0).grade() == inventory::GRADE_A, \"item 0 enum\");\n check(back.items(0).dimensions().width() == 2.5, \"nested message field\");\n check(back.items(0).source_case() == inventory::Item::kSupplier,\n \"oneof discriminator (supplier)\");\n check(back.items(0).supplier() == \"acme\", \"oneof value\");\n check(back.items(1).source_case() == inventory::Item::kWarehouse,\n \"oneof discriminator (warehouse)\");\n check(back.totals_by_grade().size() == 2, \"map size\");\n check(back.totals_by_grade().at(\"A\") == 7, \"map lookup\");\n check(google::protobuf::util::TimeUtil::TimestampToSeconds(\n back.updated_at()) == 1735689600,\n \"well-known Timestamp round trip\");\n\n // Reflection over the generated pool: the descriptor protoc emitted has to\n // describe the C++ class it emitted beside it.\n const google::protobuf::Descriptor* d = inventory::Item::descriptor();\n check(d != nullptr && d->full_name() == \"inventory.Item\",\n \"descriptor full name\");\n check(d != nullptr && d->FindFieldByName(\"sku\") != nullptr,\n \"descriptor knows the sku field\");\n check(d != nullptr && d->oneof_decl_count() == 1,\n \"descriptor knows the oneof\");\n\n google::protobuf::ShutdownProtobufLibrary();\n\n if (failures != 0) {\n std::fprintf(stderr, \"%d check(s) failed\\n\", failures);\n return 1;\n }\n std::puts(\"protoc-generated code round-trips against the linked runtime\");\n return 0;\n}\n\n#endif // _WIN32\n" + } + ] + }, + { + "project": "protobuf-upb", + "text": "# protobuf `upb` feature member. The sibling tests/examples/protobuf covers the\n# DEFAULT build, where none of upb's 64 C TUs are compiled at all.\n#\n# upb is protobuf's small C runtime, vendored in the same tarball since v22.\n# gRPC is what needs it — its generated src/core/ext/upb-gen/**.c links against\n# this runtime — so this member is the standalone proof that the feature builds\n# and works before gRPC depends on it.\n[package]\nname = \"protobuf-upb-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nprotobuf = { version = \"35.1\", features = [\"upb\"] }\n", + "files": [ + { + "path": "tests/examples/protobuf-upb/tests/upb_runtime.cpp", + "text": "// Behavioral test for compat.protobuf's `upb` feature.\n//\n// Drives BOTH runtimes across the same bytes, which is the strongest thing\n// this member can assert: the C++ runtime builds a FileDescriptorProto and\n// serializes it, then upb parses those bytes and loads them into a upb_DefPool\n// and answers reflection queries about them.\n//\n// What that reaches:\n// upb_Arena / upb_DefPool -> upb/mem/arena.c, reflection/def_pool.c\n// parse of a descriptor message -> upb/wire/decode.c, message/*.c, mini_table/*.c\n// AddFile + name lookups -> upb/reflection/{file,message,field}_def.c\n// the descriptor tables themselves -> upb/reflection/cmake/…/descriptor.upb_minitable.c\n//\n// That last one is the reason this test exists in this shape: those tables are\n// NOT in upstream's libupb_srcs, they come from cmake/libupb.cmake's\n// bootstrap_sources, and leaving them out fails only at LINK time. Anything\n// that merely compiles upb would not notice.\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n\n#include \"google/protobuf/descriptor.pb.h\"\n\n// NOT wrapped in extern \"C\": upb's headers manage their own linkage, and\n// upb/message/accessors.h deliberately declares C++ OVERLOADS outside the\n// extern \"C\" block. Forcing the whole tree into C linkage makes those overloads\n// collide with their C counterparts (\"conflicting declaration of C function\").\n#include \"google/protobuf/descriptor.upb.h\"\n#include \"upb/base/status.h\"\n#include \"upb/mem/arena.h\"\n#include \"upb/reflection/def_pool.h\"\n#include \"upb/reflection/field_def.h\"\n#include \"upb/reflection/file_def.h\"\n#include \"upb/reflection/message_def.h\"\n\nnamespace gpb = google::protobuf;\n\nnamespace {\n\n// Build the schema with the C++ runtime and hand upb its wire bytes.\nstd::string make_descriptor_bytes() {\n gpb::FileDescriptorProto file;\n file.set_name(\"mcpp_upb_test.proto\");\n file.set_package(\"mcpp.test\");\n file.set_syntax(\"proto3\");\n\n gpb::DescriptorProto* msg = file.add_message_type();\n msg->set_name(\"Person\");\n\n gpb::FieldDescriptorProto* name = msg->add_field();\n name->set_name(\"name\");\n name->set_number(1);\n name->set_type(gpb::FieldDescriptorProto::TYPE_STRING);\n name->set_label(gpb::FieldDescriptorProto::LABEL_OPTIONAL);\n\n gpb::FieldDescriptorProto* id = msg->add_field();\n id->set_name(\"id\");\n id->set_number(2);\n id->set_type(gpb::FieldDescriptorProto::TYPE_INT32);\n id->set_label(gpb::FieldDescriptorProto::LABEL_OPTIONAL);\n\n return file.SerializeAsString();\n}\n\n} // namespace\n\nint main() {\n const std::string wire = make_descriptor_bytes();\n if (wire.empty()) return 1;\n\n upb_Arena* arena = upb_Arena_New();\n if (arena == nullptr) return 1;\n\n // upb parses what the C++ runtime wrote.\n google_protobuf_FileDescriptorProto* parsed =\n google_protobuf_FileDescriptorProto_parse(wire.data(), wire.size(), arena);\n if (parsed == nullptr) {\n upb_Arena_Free(arena);\n return 1;\n }\n\n upb_DefPool* pool = upb_DefPool_New();\n if (pool == nullptr) {\n upb_Arena_Free(arena);\n return 1;\n }\n\n upb_Status status;\n upb_Status_Clear(&status);\n const upb_FileDef* file_def = upb_DefPool_AddFile(pool, parsed, &status);\n\n int rc = 1;\n if (file_def != nullptr) {\n const upb_MessageDef* msg = upb_DefPool_FindMessageByName(pool, \"mcpp.test.Person\");\n if (msg != nullptr && upb_MessageDef_FieldCount(msg) == 2) {\n const upb_FieldDef* f_name = upb_MessageDef_FindFieldByName(msg, \"name\");\n const upb_FieldDef* f_id = upb_MessageDef_FindFieldByName(msg, \"id\");\n const bool names_ok =\n std::string(upb_FileDef_Name(file_def)) == \"mcpp_upb_test.proto\" &&\n std::string(upb_MessageDef_FullName(msg)) == \"mcpp.test.Person\";\n // A name that was never declared must NOT resolve — otherwise a\n // stub lookup returning something would pass everything above.\n const bool absent_ok =\n upb_DefPool_FindMessageByName(pool, \"mcpp.test.Missing\") == nullptr;\n if (f_name != nullptr && f_id != nullptr && names_ok && absent_ok &&\n upb_FieldDef_Number(f_name) == 1 && upb_FieldDef_Number(f_id) == 2) {\n rc = 0;\n }\n }\n }\n\n upb_DefPool_Free(pool);\n upb_Arena_Free(arena);\n return rc;\n}\n" + } + ] + }, + { + "project": "protobuf", + "text": "# protobuf test project: consumes compat.protobuf (which in turn pulls\n# compat.abseil) and asserts behavior under `mcpp test`. Part of the\n# mcpp-index self-referential workspace — the workspace-root `[indices]`\n# redirect points at this repo, so both dependencies resolve to the checked-in\n# recipes (pkgs/c/compat.protobuf.lua, pkgs/c/compat.abseil.lua) rather than\n# the published remote index.\n#\n# No .proto codegen anywhere in this member, by design: compat.protobuf ships\n# the RUNTIME, not protoc. The test drives the runtime through the descriptor\n# / DynamicMessage path plus the checked-in well-known types, which is exactly\n# the surface a consumer gets without a compiler.\n[package]\nname = \"protobuf-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nprotobuf = \"35.1\"\n", + "files": [ + { + "path": "tests/examples/protobuf/tests/runtime.cpp", + "text": "// Behavioral test for compat.protobuf (the libprotobuf runtime).\n//\n// Deliberately uses NO protoc-generated code: the package ships the runtime,\n// not the compiler, so this drives the surface a consumer actually gets —\n//\n// .proto text -> descriptor compiler/parser.cc, io/tokenizer.cc\n// descriptor -> message descriptor.cc, dynamic_message.cc\n// set/get fields generated_message_reflection.cc, map_field.cc\n// serialize / parse wire_format.cc, parse_context.cc, coded_stream.cc\n// TextFormat text_format.cc\n// JSON json/json.cc + json/internal/*.cc\n// well-known types timestamp.pb.cc, struct.pb.cc, util/time_util.cc\n// MessageDifferencer util/message_differencer.cc\n// UTF-8 validation third_party/utf8_range/utf8_range.c\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n\n#include \"google/protobuf/compiler/parser.h\"\n#include \"google/protobuf/descriptor.h\"\n#include \"google/protobuf/descriptor.pb.h\"\n#include \"google/protobuf/dynamic_message.h\"\n#include \"google/protobuf/io/tokenizer.h\"\n#include \"google/protobuf/io/zero_copy_stream_impl_lite.h\"\n#include \"google/protobuf/json/json.h\"\n#include \"google/protobuf/struct.pb.h\"\n#include \"google/protobuf/text_format.h\"\n#include \"google/protobuf/timestamp.pb.h\"\n#include \"google/protobuf/util/message_differencer.h\"\n#include \"google/protobuf/util/time_util.h\"\n\nnamespace gpb = google::protobuf;\n\nnamespace {\n\n// Collects parse errors instead of printing them, so a broken schema shows up\n// as a failed assertion rather than noise on stderr.\nclass SilentErrorCollector : public gpb::io::ErrorCollector {\npublic:\n void RecordError(int line, gpb::io::ColumnNumber col, absl::string_view msg) override {\n ++errors;\n (void)line;\n (void)col;\n (void)msg;\n }\n int errors = 0;\n};\n\nconstexpr const char* kSchema = R\"(\n syntax = \"proto3\";\n package mcpp.test;\n message Person {\n string name = 1;\n int32 id = 2;\n repeated string tags = 3;\n map scores = 4;\n }\n)\";\n\n// Parse the .proto TEXT above into a FileDescriptorProto. This is the runtime's\n// own parser (compiler/parser.cc), not protoc.\nbool build_pool(gpb::DescriptorPool& pool, const gpb::Descriptor** out) {\n gpb::io::ArrayInputStream raw(kSchema, static_cast(std::string(kSchema).size()));\n SilentErrorCollector collector;\n gpb::io::Tokenizer tokenizer(&raw, &collector);\n\n gpb::FileDescriptorProto file;\n gpb::compiler::Parser parser;\n parser.RecordErrorsTo(&collector);\n if (!parser.Parse(&tokenizer, &file) || collector.errors != 0) return false;\n file.set_name(\"mcpp_test.proto\");\n\n const gpb::FileDescriptor* fd = pool.BuildFile(file);\n if (fd == nullptr) return false;\n *out = pool.FindMessageTypeByName(\"mcpp.test.Person\");\n return *out != nullptr;\n}\n\nbool dynamic_message_roundtrip(const gpb::Descriptor* desc) {\n gpb::DynamicMessageFactory factory;\n const gpb::Message* prototype = factory.GetPrototype(desc);\n if (prototype == nullptr) return false;\n\n std::unique_ptr msg(prototype->New());\n const gpb::Reflection* ref = msg->GetReflection();\n\n ref->SetString(msg.get(), desc->FindFieldByName(\"name\"), \"Ada\");\n ref->SetInt32(msg.get(), desc->FindFieldByName(\"id\"), 1815);\n const gpb::FieldDescriptor* tags = desc->FindFieldByName(\"tags\");\n ref->AddString(msg.get(), tags, \"math\");\n ref->AddString(msg.get(), tags, \"engine\");\n\n // Wire round-trip.\n std::string wire;\n if (!msg->SerializeToString(&wire)) return false;\n std::unique_ptr back(prototype->New());\n if (!back->ParseFromString(wire)) return false;\n if (!gpb::util::MessageDifferencer::Equals(*msg, *back)) return false;\n\n // TextFormat round-trip.\n std::string text;\n if (!gpb::TextFormat::PrintToString(*back, &text)) return false;\n if (text.find(\"name: \\\"Ada\\\"\") == std::string::npos) return false;\n if (text.find(\"id: 1815\") == std::string::npos) return false;\n std::unique_ptr from_text(prototype->New());\n if (!gpb::TextFormat::ParseFromString(text, from_text.get())) return false;\n if (!gpb::util::MessageDifferencer::Equals(*msg, *from_text)) return false;\n\n // A truncated payload must be REJECTED — proves parsing is real and not a\n // no-op that accepts anything.\n std::unique_ptr truncated(prototype->New());\n if (!wire.empty() && truncated->ParseFromString(wire.substr(0, wire.size() - 1))) return false;\n\n return ref->GetRepeatedString(*back, tags, 1) == \"engine\";\n}\n\nbool json_ok(const gpb::Descriptor* desc) {\n gpb::DynamicMessageFactory factory;\n std::unique_ptr msg(factory.GetPrototype(desc)->New());\n const gpb::Reflection* ref = msg->GetReflection();\n ref->SetString(msg.get(), desc->FindFieldByName(\"name\"), \"Grace\");\n ref->SetInt32(msg.get(), desc->FindFieldByName(\"id\"), 1906);\n\n std::string json;\n if (!gpb::json::MessageToJsonString(*msg, &json).ok()) return false;\n if (json.find(\"\\\"name\\\":\\\"Grace\\\"\") == std::string::npos) return false;\n\n std::unique_ptr back(factory.GetPrototype(desc)->New());\n if (!gpb::json::JsonStringToMessage(json, back.get()).ok()) return false;\n return gpb::util::MessageDifferencer::Equals(*msg, *back);\n}\n\n// The well-known types are generated code that upstream CHECKS IN, so they are\n// compiled into this package and usable with no protoc anywhere.\nbool well_known_types_ok() {\n gpb::Timestamp ts;\n if (!gpb::util::TimeUtil::FromString(\"2026-08-04T12:34:56Z\", &ts)) return false;\n if (gpb::util::TimeUtil::ToString(ts) != \"2026-08-04T12:34:56Z\") return false;\n\n gpb::Struct s;\n (*s.mutable_fields())[\"pi\"].set_number_value(3.5);\n (*s.mutable_fields())[\"name\"].set_string_value(\"mcpp\");\n if (s.fields().size() != 2) return false;\n if (s.fields().at(\"pi\").number_value() != 3.5) return false;\n\n std::string wire;\n if (!s.SerializeToString(&wire)) return false;\n gpb::Struct back;\n return back.ParseFromString(wire) && back.fields().at(\"name\").string_value() == \"mcpp\";\n}\n\n// proto3 string fields are UTF-8 validated, which routes into\n// third_party/utf8_range/utf8_range.c — the one C TU in this package.\nbool utf8_validation_ok(const gpb::Descriptor* desc) {\n gpb::DynamicMessageFactory factory;\n std::unique_ptr msg(factory.GetPrototype(desc)->New());\n msg->GetReflection()->SetString(msg.get(), desc->FindFieldByName(\"name\"), \"héllo-世界\");\n\n std::string wire;\n if (!msg->SerializeToString(&wire)) return false;\n std::unique_ptr back(factory.GetPrototype(desc)->New());\n if (!back->ParseFromString(wire)) return false;\n return back->GetReflection()->GetString(*back, desc->FindFieldByName(\"name\")) == \"héllo-世界\";\n}\n\n} // namespace\n\nint main() {\n gpb::DescriptorPool pool;\n const gpb::Descriptor* person = nullptr;\n if (!build_pool(pool, &person)) return 1;\n\n const bool ok = dynamic_message_roundtrip(person) && json_ok(person)\n && well_known_types_ok() && utf8_validation_ok(person);\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "quickjs-ng", + "text": "# QuickJS-ng test project: evaluate JavaScript through the engine and assert\n# the values it computes, one group per compiled source file.\n#\n# compat.quickjs-ng carries a `linux` section only (see the descriptor's\n# header), so the dependency is gated and the test compiles to a no-op main()\n# elsewhere, the same shape as tests/examples/libaio and tests/examples/wamr.\n[package]\nname = \"quickjs-ng-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nquickjs-ng = \"0.17.0\"\n", + "files": [ + { + "path": "tests/examples/quickjs-ng/tests/engine.cpp", + "text": "// Behavioral test: JavaScript evaluated by the engine, checked by value.\n//\n// The package compiles four sources, and each group below needs one of them to\n// be present and built with the right flags, so a missing or mis-built TU shows\n// up as a wrong answer rather than only as a link error:\n//\n// quickjs.c evaluation, closures, exceptions, the promise job queue\n// dtoa.c shortest round-trip number formatting (0.1 + 0.2, 5e-324)\n// libregexp.c named groups, case-insensitive and sticky matching\n// libunicode.c full case mapping, NFC normalization, script properties\n//\n// The expected strings are what the ECMAScript specification requires, written\n// out by hand; none is computed by the code under test.\n//\n// The runtime is freed at the end. The package does not define NDEBUG, so\n// JS_FreeRuntime asserts that no object is still alive (quickjs.c:2699) and an\n// object this test forgot to free aborts it.\n\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n\n// The descriptor exposes quickjs.h alone. The engine's private headers sit next\n// to it in the tarball and must not reach a consumer.\n#if __has_include() || __has_include()\n#error \"compat.quickjs-ng put the engine's private headers on the include path\"\n#endif\n\nnamespace {\n\nint failures = 0;\n\nstd::string to_std_string(JSContext* ctx, JSValueConst v) {\n const char* s = JS_ToCString(ctx, v);\n std::string out = s ? s : \"\";\n JS_FreeCString(ctx, s);\n return out;\n}\n\n// Global code; the completion value of the last statement is the result.\nstd::string run_js(JSContext* ctx, const char* src) {\n JSValue v = JS_Eval(ctx, src, std::strlen(src), \"\", JS_EVAL_TYPE_GLOBAL);\n std::string out;\n if (JS_IsException(v)) {\n JSValue exc = JS_GetException(ctx);\n out = \"exception \" + to_std_string(ctx, exc);\n JS_FreeValue(ctx, exc);\n } else {\n out = to_std_string(ctx, v);\n }\n JS_FreeValue(ctx, v);\n return out;\n}\n\nvoid expect(JSContext* ctx, const char* src, const char* want) {\n std::string got = run_js(ctx, src);\n if (got != want) {\n std::fprintf(stderr, \"FAIL %s\\n want: %s\\n got: %s\\n\", src, want, got.c_str());\n ++failures;\n }\n}\n\n} // namespace\n\nint main() {\n // The library and the header it was built from agree on the version.\n char header_version[32];\n std::snprintf(header_version, sizeof header_version, \"%d.%d.%d%s\",\n QJS_VERSION_MAJOR, QJS_VERSION_MINOR, QJS_VERSION_PATCH, QJS_VERSION_SUFFIX);\n std::printf(\"QuickJS-ng %s\\n\", JS_GetVersion());\n if (std::strcmp(JS_GetVersion(), \"0.17.0\") != 0 ||\n std::strcmp(JS_GetVersion(), header_version) != 0) {\n std::fprintf(stderr, \"FAIL version: library %s, header %s\\n\", JS_GetVersion(), header_version);\n ++failures;\n }\n\n JSRuntime* rt = JS_NewRuntime();\n JSContext* ctx = rt ? JS_NewContext(rt) : nullptr;\n if (ctx == nullptr) {\n std::fprintf(stderr, \"FAIL could not create a runtime and context\\n\");\n return 1;\n }\n\n // quickjs.c\n expect(ctx, \"const sq = [1, 2, 3].map(x => x * x); sq.reduce((a, b) => a + b, 0)\", \"14\");\n expect(ctx, \"(() => { let n = 0; const inc = () => ++n; inc(); inc(); return inc(); })()\", \"3\");\n expect(ctx, \"JSON.stringify({ a: [1, { b: null }], c: 'x' })\", \"{\\\"a\\\":[1,{\\\"b\\\":null}],\\\"c\\\":\\\"x\\\"}\");\n expect(ctx, \"throw new TypeError('boom')\", \"exception TypeError: boom\");\n\n // dtoa.c\n expect(ctx, \"String(0.1 + 0.2)\", \"0.30000000000000004\");\n expect(ctx, \"String(5e-324)\", \"5e-324\");\n expect(ctx, \"String(1e21)\", \"1e+21\");\n expect(ctx, \"(1.005).toFixed(2)\", \"1.00\");\n expect(ctx, \"(255).toString(16) + ' ' + parseFloat('2.5e-3') * 4\", \"ff 0.01\");\n\n // libregexp.c\n expect(ctx, \"const m = /(?\\\\d{4})-(?\\\\d{2})/.exec('on 2026-09'); m.groups.y + '/' + m.groups.m\",\n \"2026/09\");\n expect(ctx, \"'aXbxc'.split(/x/i).join(',')\", \"a,b,c\");\n expect(ctx, \"const re = /o/y; re.lastIndex = 1; re.test('foo') + ' ' + re.lastIndex\", \"true 2\");\n\n // libunicode.c\n expect(ctx, \"'straße'.toUpperCase()\", \"STRASSE\");\n expect(ctx, \"'\\\\u0041\\\\u030A'.normalize('NFC') === '\\\\u00C5'\", \"true\");\n expect(ctx, \"/^\\\\p{Script=Greek}+$/u.test('αβγ') + ' ' + /\\\\p{Script=Greek}/u.test('abc')\", \"true false\");\n\n // quickjs.c, the job queue: a reaction runs only when the host drains it.\n expect(ctx, \"var out = 'pending'; Promise.resolve(20).then(v => { out = 'resolved ' + (v + 1); }); out\",\n \"pending\");\n JSContext* job_ctx = nullptr;\n int r;\n while ((r = JS_ExecutePendingJob(rt, &job_ctx)) > 0) {\n }\n if (r != 0) {\n std::fprintf(stderr, \"FAIL a pending job threw\\n\");\n ++failures;\n }\n expect(ctx, \"out\", \"resolved 21\");\n\n JS_FreeContext(ctx);\n JS_FreeRuntime(rt);\n\n if (failures != 0) {\n std::fprintf(stderr, \"%d check(s) failed\\n\", failures);\n return 1;\n }\n return 0;\n}\n\n#else\n\nint main() { return 0; } // compat.quickjs-ng declares linux only; nothing to assert here.\n\n#endif\n" + } + ] + }, + { + "project": "re2", + "text": "# re2 test project: consumes compat.re2 and asserts behavior under `mcpp test`.\n# Part of the mcpp-index self-referential workspace — the workspace-root\n# `[indices]` redirect points at this repo, so the dependency resolves to the\n# checked-in recipe (pkgs/c/compat.re2.lua) rather than the published index.\n[package]\nname = \"re2-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nre2 = \"2022-04-01\"\n", + "files": [ + { + "path": "tests/examples/re2/tests/match.cpp", + "text": "// Behavioral test for compat.re2.\n//\n// Exercises the parts of RE2 that gRPC's xds matchers actually use — full and\n// partial match with captures, a pre-compiled RE2 object, replacement, and the\n// RE2::Set multi-pattern API — so a package that compiled but linked nothing\n// useful cannot pass:\n//\n// RE2 ctor / FullMatch / PartialMatch -> re2/re2.cc, parse.cc, compile.cc, prog.cc\n// captures + rewriting -> re2/re2.cc, regexp.cc, simplify.cc\n// RE2::Set -> re2/set.cc, dfa.cc, nfa.cc\n// invalid pattern reporting -> re2/parse.cc, tostring.cc\n// UTF-8 handling -> util/rune.cc, unicode_groups.cc\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n\n#include \"re2/re2.h\"\n#include \"re2/set.h\"\n\nnamespace {\n\nbool matching_ok() {\n if (!RE2::FullMatch(\"grpc-1.83.0\", R\"(grpc-\\d+\\.\\d+\\.\\d+)\")) return false;\n // A full match must NOT accept trailing junk — proves this is FullMatch\n // semantics and not an accidental substring search.\n if (RE2::FullMatch(\"grpc-1.83.0-rc1\", R\"(grpc-\\d+\\.\\d+\\.\\d+)\")) return false;\n return RE2::PartialMatch(\"grpc-1.83.0-rc1\", R\"(grpc-\\d+\\.\\d+\\.\\d+)\");\n}\n\nbool captures_ok() {\n const RE2 re(R\"((\\w+)://([^/:]+):(\\d+))\"); // pre-compiled, the way a matcher holds it\n if (!re.ok()) return false;\n\n std::string scheme, host;\n int port = 0;\n if (!RE2::FullMatch(\"dns://localhost:50051\", re, &scheme, &host, &port)) return false;\n return scheme == \"dns\" && host == \"localhost\" && port == 50051;\n}\n\nbool rewrite_ok() {\n std::string s = \"service/Echo, service/Ping\";\n const int n = RE2::GlobalReplace(&s, R\"(service/(\\w+))\", R\"(grpc.\\1)\");\n return n == 2 && s == \"grpc.Echo, grpc.Ping\";\n}\n\nbool set_ok() {\n RE2::Set set(RE2::DefaultOptions, RE2::UNANCHORED);\n const int a = set.Add(R\"(^/helloworld\\.Greeter/)\", nullptr);\n const int b = set.Add(R\"(SayHello$)\", nullptr);\n if (a < 0 || b < 0 || !set.Compile()) return false;\n\n std::vector hits;\n if (!set.Match(\"/helloworld.Greeter/SayHello\", &hits)) return false;\n if (hits.size() != 2) return false;\n\n std::vector none;\n return !set.Match(\"/other.Service/Method\", &none);\n}\n\nbool invalid_pattern_reported() {\n // An unbalanced group must be REJECTED with an error string, not silently\n // accepted — otherwise a broken matcher config would look valid.\n // log_errors(false) keeps RE2 from also printing the (expected) parse\n // failure to stderr, so real problems stay the only noise in the log.\n RE2::Options quiet;\n quiet.set_log_errors(false);\n const RE2 bad(\"(unclosed\", quiet);\n return !bad.ok() && !bad.error().empty();\n}\n\nbool utf8_ok() {\n // '.' counts runes, not bytes, in UTF-8 mode (the default).\n return RE2::FullMatch(\"héllo\", \"h.llo\") && RE2::PartialMatch(\"世界\", \"界\");\n}\n\n} // namespace\n\nint main() {\n const bool ok = matching_ok() && captures_ok() && rewrite_ok() && set_ok()\n && invalid_pattern_reported() && utf8_ok();\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "recastnavigation-features", + "text": "# recastnavigation-features 测试工程:以三个可选 feature 全开的方式消费\n# compat.recastnavigation——`crowd`、`tilecache`、`debug-utils`——并逐个断言\n# 它们干了真实的活。mcpp-index 自引用 workspace 的一员;workspace 根的\n# [indices] 重定向让依赖解析到签入的描述符\n# (pkgs/c/compat.recastnavigation.lua)。\n#\n# 兄弟成员 tests/examples/recastnavigation 覆盖无 feature 的核心构建\n# (Recast + Detour);本成员证明三个被门控的组件——每个都只在被点名时\n# 才编进库里:\n#\n# crowd -> 一个 agent 被引导着穿过网格,而且必须真的挪了地方\n# tilecache -> 构建高度场层、压缩、缓存,再物化回一块导航网格 tile\n# debug-utils -> 用一个 duDebugDraw 实现驱动导航网格绘制,并给收到的\n# 图元计数\n#\n# `debug-utils` 会传递拉入 `tilecache`(上游的 DebugUtils 无条件链接\n# DetourTileCache),所以只点调试绘制的消费者依然能拿到可链接的库——\n# 这条 implication 也是本成员要守住的东西之一。\n[package]\nname = \"recastnavigation-features-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nrecastnavigation = { version = \"1.6.0\", features = [\"crowd\", \"debug-utils\"] }\n", + "files": [ + { + "path": "tests/examples/recastnavigation-features/tests/features.cpp", + "text": "// compat.recastnavigation——三个按 feature 门控的组件,逐个让它做出\n// 可观察的工作。\n//\n// `recastnavigation-features` 只点名 `crowd` 和 `debug-utils`。\n// `tilecache` 没被点名,这是刻意的:上游的 DebugUtils 无条件链接\n// DetourTileCache,所以描述符让 `debug-utils` imply `tilecache`。这条\n// implication 哪天失效,本成员会在链接期失败,而不是悄悄发出一个没法\n// 画调试图的包。\n//\n// 一切都通过生成的 `recastnavigation/` include 前缀行使——与上游自己的\n// CMake 安装产出一致——所以 shim 层对 feature 头也有覆盖,不只是核心头。\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nimport std;\n\nnamespace {\n\nconstexpr float kSize = 20.0f;\n\nconstexpr float kAgentHeight = 2.0f;\nconstexpr float kAgentRadius = 0.6f;\nconstexpr float kAgentClimb = 0.9f;\n\n// y=0 处一块 20x20 的平面四边形,俯视为顺时针绕序,使 rcCalcTriNormal\n// 产出朝上的法线——rcMarkWalkableTriangles 测试 `norm[1] > threshold`,\n// 绕序反了不会报错,只会得到空网格。\nconstexpr float kFloorVerts[] = { 0, 0, 0, 0, 0, kSize, kSize, 0, kSize, kSize, 0, 0 };\nconstexpr int kFloorTris[] = { 0, 1, 2, 0, 2, 3 };\n\nvoid fillConfig(rcConfig& cfg, float cs) {\n cfg = rcConfig{};\n cfg.cs = cs;\n cfg.ch = 0.2f;\n cfg.walkableSlopeAngle = 45.0f;\n cfg.walkableHeight = static_cast(std::ceil(kAgentHeight / cfg.ch));\n cfg.walkableClimb = static_cast(std::floor(kAgentClimb / cfg.ch));\n cfg.walkableRadius = static_cast(std::ceil(kAgentRadius / cfg.cs));\n cfg.maxEdgeLen = static_cast(12.0f / cfg.cs);\n cfg.maxSimplificationError = 1.3f;\n cfg.minRegionArea = static_cast(rcSqr(8.0f));\n cfg.mergeRegionArea = static_cast(rcSqr(20.0f));\n cfg.maxVertsPerPoly = 6;\n cfg.detailSampleDist = cfg.cs * 6.0f;\n cfg.detailSampleMaxError = cfg.ch * 1.0f;\n cfg.bmin[0] = 0.0f; cfg.bmin[1] = 0.0f; cfg.bmin[2] = 0.0f;\n cfg.bmax[0] = kSize; cfg.bmax[1] = 0.0f; cfg.bmax[2] = kSize;\n}\n\n// 两条流水线共用的前半段:栅格化、过滤、压缩。\nrcCompactHeightfield* buildCompact(rcContext& ctx, const rcConfig& cfg) {\n rcHeightfield* solid = rcAllocHeightfield();\n if (!solid) return nullptr;\n if (!rcCreateHeightfield(&ctx, *solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)) {\n rcFreeHeightField(solid);\n return nullptr;\n }\n unsigned char triareas[2] = { 0, 0 };\n rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, kFloorVerts, 4, kFloorTris, 2, triareas);\n rcRasterizeTriangles(&ctx, kFloorVerts, 4, kFloorTris, triareas, 2, *solid, cfg.walkableClimb);\n rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *solid);\n rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *solid);\n rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *solid);\n\n rcCompactHeightfield* chf = rcAllocCompactHeightfield();\n if (!chf || !rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *solid, *chf)) {\n rcFreeCompactHeightfield(chf);\n rcFreeHeightField(solid);\n return nullptr;\n }\n rcFreeHeightField(solid);\n return chf;\n}\n\n// dtTileCacheCompressor 是用户提供的接口——RecastDemo 往里接的是 fastlz。\n// 对一个只关心 tile 能完整往返的测试来说,memcpy 就是很好的压缩方案,\n// 还顺带把 fastlz 依赖挡在外面。\nstruct PassthroughCompressor final : dtTileCacheCompressor {\n int maxCompressedSize(const int bufferSize) override { return bufferSize; }\n dtStatus compress(const unsigned char* buffer, const int bufferSize,\n unsigned char* compressed, const int maxCompressedSize, int* compressedSize) override {\n if (bufferSize > maxCompressedSize) return DT_FAILURE;\n std::memcpy(compressed, buffer, static_cast(bufferSize));\n *compressedSize = bufferSize;\n return DT_SUCCESS;\n }\n dtStatus decompress(const unsigned char* compressed, const int compressedSize,\n unsigned char* buffer, const int maxBufferSize, int* bufferSize) override {\n if (compressedSize > maxBufferSize) return DT_FAILURE;\n std::memcpy(buffer, compressed, static_cast(compressedSize));\n *bufferSize = compressedSize;\n return DT_SUCCESS;\n }\n};\n\n// dtTileCache 把每块刚建好的 tile 交给 mesh process 打 flag。没有这一步,\n// 多边形的 flags 会是 0,什么都查不了——这正是「tile 存在」这条断言\n// 抓不住的失败模式。\nstruct FlagWalkable final : dtTileCacheMeshProcess {\n void process(dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags) override {\n for (int i = 0; i < params->polyCount; ++i)\n polyFlags[i] = (polyAreas[i] == DT_TILECACHE_WALKABLE_AREA) ? 1 : 0;\n }\n};\n\n// 给 DebugUtils 向渲染器要的东西计数。duDebugDraw 是抽象接口,实现一个\n// 是证明 DebugUtils 既编译过又链接得上的唯一办法——只做头的 DebugUtils\n// 撑不过 begin()。\nstruct RecordingDraw final : duDebugDraw {\n int vertices = 0;\n int begins = 0;\n int triBatches = 0;\n\n void depthMask(bool) override {}\n void texture(bool) override {}\n void begin(duDebugDrawPrimitives prim, float) override {\n ++begins;\n if (prim == DU_DRAW_TRIS) ++triBatches;\n }\n void vertex(const float*, unsigned int) override { ++vertices; }\n void vertex(const float, const float, const float, unsigned int) override { ++vertices; }\n void vertex(const float*, unsigned int, const float*) override { ++vertices; }\n void vertex(const float, const float, const float, unsigned int, const float, const float) override { ++vertices; }\n void end() override {}\n};\n\n} // namespace\n\nint main() {\n bool ok = true;\n const auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n rcContext ctx;\n\n // ══ Recast:一张独立网格,crowd 和 DebugUtils 都要用它 ═════════════════\n rcConfig cfg;\n fillConfig(cfg, 0.5f);\n rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);\n\n rcCompactHeightfield* chf = buildCompact(ctx, cfg);\n check(chf != nullptr, \"compact heightfield\");\n\n rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf);\n rcBuildDistanceField(&ctx, *chf);\n rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea);\n\n rcContourSet* cset = rcAllocContourSet();\n rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset);\n rcPolyMesh* pmesh = rcAllocPolyMesh();\n rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *pmesh);\n rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail();\n rcBuildPolyMeshDetail(&ctx, *pmesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dmesh);\n std::println(\"solo poly mesh: {} verts, {} polys\", pmesh->nverts, pmesh->npolys);\n check(pmesh->npolys > 0, \"core Recast build produced polygons\");\n rcFreeCompactHeightfield(chf);\n rcFreeContourSet(cset);\n\n for (int i = 0; i < pmesh->npolys; ++i)\n pmesh->flags[i] = (pmesh->areas[i] == RC_WALKABLE_AREA) ? 1 : 0;\n\n dtNavMeshCreateParams params{};\n params.verts = pmesh->verts;\n params.vertCount = pmesh->nverts;\n params.polys = pmesh->polys;\n params.polyFlags = pmesh->flags;\n params.polyAreas = pmesh->areas;\n params.polyCount = pmesh->npolys;\n params.nvp = pmesh->nvp;\n params.detailMeshes = dmesh->meshes;\n params.detailVerts = dmesh->verts;\n params.detailVertsCount = dmesh->nverts;\n params.detailTris = dmesh->tris;\n params.detailTriCount = dmesh->ntris;\n params.walkableHeight = kAgentHeight;\n params.walkableRadius = kAgentRadius;\n params.walkableClimb = kAgentClimb;\n rcVcopy(params.bmin, pmesh->bmin);\n rcVcopy(params.bmax, pmesh->bmax);\n params.cs = cfg.cs;\n params.ch = cfg.ch;\n params.buildBvTree = true;\n\n unsigned char* navData = nullptr;\n int navDataSize = 0;\n check(dtCreateNavMeshData(¶ms, &navData, &navDataSize), \"dtCreateNavMeshData\");\n\n dtNavMesh* nav = dtAllocNavMesh();\n check(nav != nullptr, \"dtAllocNavMesh\");\n check(dtStatusSucceed(nav->init(navData, navDataSize, DT_TILE_FREE_DATA)), \"dtNavMesh::init\");\n dtNavMeshQuery* query = dtAllocNavMeshQuery();\n check(dtStatusSucceed(query->init(nav, 2048)), \"dtNavMeshQuery::init\");\n\n const float ext[3] = { 2.0f, 4.0f, 2.0f };\n dtQueryFilter filter;\n filter.setIncludeFlags(1);\n filter.setExcludeFlags(0);\n\n // ══ crowd ══════════════════════════════════════════════════════════════\n dtCrowd crowd;\n check(crowd.init(4, kAgentRadius, nav), \"dtCrowd::init\");\n\n dtCrowdAgentParams agentParams{};\n agentParams.radius = kAgentRadius;\n agentParams.height = kAgentHeight;\n agentParams.maxAcceleration = 8.0f;\n agentParams.maxSpeed = 3.5f;\n agentParams.collisionQueryRange = kAgentRadius * 12.0f;\n agentParams.pathOptimizationRange = kAgentRadius * 30.0f;\n agentParams.updateFlags = DT_CROWD_ANTICIPATE_TURNS | DT_CROWD_OBSTACLE_AVOIDANCE | DT_CROWD_OPTIMIZE_VIS;\n agentParams.obstacleAvoidanceType = 3;\n agentParams.separationWeight = 2.0f;\n\n const float start[3] = { 2.0f, 0.2f, 2.0f };\n const float goal[3] = { 18.0f, 0.2f, 18.0f };\n const int agent = crowd.addAgent(start, &agentParams);\n check(agent >= 0, \"crowd.addAgent\");\n\n dtPolyRef goalRef = 0;\n float goalNear[3] = { 0, 0, 0 };\n check(dtStatusSucceed(query->findNearestPoly(goal, ext, &filter, &goalRef, goalNear)), \"findNearestPoly(goal)\");\n check(crowd.requestMoveTarget(agent, goalRef, goalNear), \"crowd.requestMoveTarget\");\n\n const float distanceBefore = dtVdist(start, goal);\n for (int i = 0; i < 400; ++i) crowd.update(1.0f / 60.0f, nullptr);\n const dtCrowdAgent* moved = crowd.getAgent(agent);\n check(moved != nullptr, \"crowd agent survives 400 updates\");\n if (moved) {\n const float distanceAfter = dtVdist(moved->npos, goal);\n std::println(\"crowd: {} -> {} from goal (state {})\", distanceBefore, distanceAfter, moved->state);\n // dtCrowd 在这里约 6.7 秒的模拟时间内完成引导;一步都没有积分过的\n // agent 会仍然待在起点。\n check(distanceAfter < distanceBefore - 5.0f, \"crowd agent actually walked toward the goal\");\n }\n\n // ══ tilecache ══════════════════════════════════════════════════════════\n // rcBuildHeightfieldLayers 需要可走区域外有一圈不可通行的边界,这就是\n // 为什么这里是第二次、单独配置的栅格化,而不是复用上面的 compact\n // heightfield。\n rcConfig tcfg;\n fillConfig(tcfg, 0.5f);\n const int tileSizeVoxels = 40; // 一块 tile 盖住整个地面\n tcfg.walkableRadius = static_cast(std::ceil(kAgentRadius / tcfg.cs));\n tcfg.tileSize = tileSizeVoxels;\n tcfg.borderSize = tcfg.walkableRadius + 3;\n const float tileWorldSize = static_cast(tileSizeVoxels) * tcfg.cs;\n tcfg.bmin[0] -= tcfg.borderSize * tcfg.cs;\n tcfg.bmin[2] -= tcfg.borderSize * tcfg.cs;\n tcfg.bmax[0] += tcfg.borderSize * tcfg.cs;\n tcfg.bmax[2] += tcfg.borderSize * tcfg.cs;\n rcCalcGridSize(tcfg.bmin, tcfg.bmax, tcfg.cs, &tcfg.width, &tcfg.height);\n\n rcCompactHeightfield* tchf = buildCompact(ctx, tcfg);\n check(tchf != nullptr, \"tiled compact heightfield\");\n rcErodeWalkableArea(&ctx, tcfg.walkableRadius, *tchf);\n\n rcHeightfieldLayerSet* lset = rcAllocHeightfieldLayerSet();\n check(rcBuildHeightfieldLayers(&ctx, *tchf, tcfg.borderSize, tcfg.walkableHeight, *lset),\n \"rcBuildHeightfieldLayers\");\n std::println(\"heightfield layers: {}\", lset->nlayers);\n check(lset->nlayers > 0, \"heightfield layers were built\");\n rcFreeCompactHeightfield(tchf);\n\n dtNavMesh* tiledNav = dtAllocNavMesh();\n dtNavMeshParams navParams{};\n rcVcopy(navParams.orig, cfg.bmin);\n navParams.tileWidth = tileWorldSize;\n navParams.tileHeight = tileWorldSize;\n navParams.maxTiles = 8;\n navParams.maxPolys = 128;\n check(dtStatusSucceed(tiledNav->init(&navParams)), \"tiled navmesh init\");\n\n PassthroughCompressor compressor;\n FlagWalkable meshProcess;\n dtTileCacheAlloc tileAlloc;\n dtTileCache tileCache;\n\n dtTileCacheParams tcParams{};\n rcVcopy(tcParams.orig, cfg.bmin);\n tcParams.cs = tcfg.cs;\n tcParams.ch = tcfg.ch;\n tcParams.width = tileSizeVoxels;\n tcParams.height = tileSizeVoxels;\n tcParams.walkableHeight = kAgentHeight;\n tcParams.walkableRadius = kAgentRadius;\n tcParams.walkableClimb = kAgentClimb;\n tcParams.maxSimplificationError = tcfg.maxSimplificationError;\n tcParams.maxTiles = 8;\n tcParams.maxObstacles = 32;\n check(dtStatusSucceed(tileCache.init(&tcParams, &tileAlloc, &compressor, &meshProcess)), \"dtTileCache::init\");\n\n const rcHeightfieldLayer* rlayer = &lset->layers[0];\n dtTileCacheLayerHeader header{};\n header.magic = DT_TILECACHE_MAGIC;\n header.version = DT_TILECACHE_VERSION;\n header.tx = 0;\n header.ty = 0;\n header.tlayer = 0;\n rcVcopy(header.bmin, rlayer->bmin);\n rcVcopy(header.bmax, rlayer->bmax);\n header.width = static_cast(rlayer->width);\n header.height = static_cast(rlayer->height);\n header.minx = static_cast(rlayer->minx);\n header.maxx = static_cast(rlayer->maxx);\n header.miny = static_cast(rlayer->miny);\n header.maxy = static_cast(rlayer->maxy);\n header.hmin = static_cast(rlayer->hmin);\n header.hmax = static_cast(rlayer->hmax);\n\n // DetourTileCache 的 builder 半边,在同一层上端到端驱动一遍。\n dtTileCacheLayer layer{};\n layer.header = &header;\n layer.heights = rlayer->heights;\n layer.areas = rlayer->areas;\n layer.cons = rlayer->cons;\n layer.regs = static_cast(dtAlloc(rlayer->width * rlayer->height, DT_ALLOC_TEMP));\n std::memset(layer.regs, 0xff, static_cast(rlayer->width) * rlayer->height);\n\n check(dtStatusSucceed(dtBuildTileCacheRegions(&tileAlloc, layer, tcfg.walkableClimb)),\n \"dtBuildTileCacheRegions\");\n // dtFreeTileCacheContourSet / dtFreeTileCachePolyMesh 连容器本身带载荷\n // 一起释放,所以两者都必须用配套的 dtAlloc* 辅助函数从堆上取——\n // 释放栈上对象会直接崩。\n dtTileCacheContourSet* contours = dtAllocTileCacheContourSet(&tileAlloc);\n check(contours != nullptr, \"dtAllocTileCacheContourSet\");\n check(dtStatusSucceed(dtBuildTileCacheContours(&tileAlloc, layer, tcfg.walkableClimb,\n tcfg.maxSimplificationError, *contours)),\n \"dtBuildTileCacheContours\");\n dtTileCachePolyMesh* layerMesh = dtAllocTileCachePolyMesh(&tileAlloc);\n check(layerMesh != nullptr, \"dtAllocTileCachePolyMesh\");\n check(dtStatusSucceed(dtBuildTileCachePolyMesh(&tileAlloc, *contours, *layerMesh)), \"dtBuildTileCachePolyMesh\");\n std::println(\"tile cache poly mesh: {} polys\", layerMesh->npolys);\n check(layerMesh->npolys > 0, \"tile cache builder produced polygons\");\n dtFreeTileCacheContourSet(&tileAlloc, contours);\n dtFreeTileCachePolyMesh(&tileAlloc, layerMesh);\n dtFree(layer.regs);\n\n // 缓存半边:压缩这一层,交给 dtTileCache,再让它从中物化出一块\n // 导航网格 tile。\n unsigned char* tileData = nullptr;\n int tileDataSize = 0;\n check(dtStatusSucceed(dtBuildTileCacheLayer(&compressor, &header, rlayer->heights, rlayer->areas,\n rlayer->cons, &tileData, &tileDataSize)),\n \"dtBuildTileCacheLayer\");\n dtCompressedTileRef tileRef = 0;\n check(dtStatusSucceed(tileCache.addTile(tileData, tileDataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef)),\n \"dtTileCache::addTile\");\n check(tileRef != 0, \"addTile returned a reference\");\n check(dtStatusSucceed(tileCache.buildNavMeshTile(tileRef, tiledNav)), \"dtTileCache::buildNavMeshTile\");\n\n const dtMeshTile* built = tiledNav->getTileAt(0, 0, 0);\n check(built != nullptr, \"the tile cache materialized a navmesh tile\");\n if (built && built->header) std::println(\"cached tile polys: {}\", built->header->polyCount);\n check(built != nullptr && built->header != nullptr && built->header->polyCount > 0,\n \"that tile holds polygons\");\n\n // ……以及它是可用的,而不只是存在:多边形 flags 为 0 的网格照样有\n // 几何,却什么查询都答不了。\n dtNavMeshQuery* tiledQuery = dtAllocNavMeshQuery();\n check(dtStatusSucceed(tiledQuery->init(tiledNav, 128)), \"tiled query init\");\n dtPolyRef tiledRef = 0;\n float tiledNear[3] = { 0, 0, 0 };\n const float middle[3] = { 10.0f, 0.2f, 10.0f };\n check(dtStatusSucceed(tiledQuery->findNearestPoly(middle, ext, &filter, &tiledRef, tiledNear)),\n \"findNearestPoly on the cached tile\");\n check(tiledRef != 0, \"the cached tile answers queries\");\n dtFreeNavMeshQuery(tiledQuery);\n\n // ══ debug-utils ════════════════════════════════════════════════════════\n RecordingDraw draw;\n duDebugDrawNavMesh(&draw, *nav, 0);\n std::println(\"debug draw: {} begins ({} triangle batches), {} vertices\",\n draw.begins, draw.triBatches, draw.vertices);\n check(draw.vertices > 0, \"DebugUtils emitted vertices for the navmesh\");\n check(draw.triBatches > 0, \"DebugUtils emitted triangle batches\");\n\n dtFreeNavMesh(tiledNav);\n dtFreeNavMeshQuery(query);\n dtFreeNavMesh(nav);\n rcFreeHeightfieldLayerSet(lset);\n rcFreePolyMesh(pmesh);\n rcFreePolyMeshDetail(dmesh);\n\n std::println(\"compat.recastnavigation feature test: {}\", ok ? \"ok\" : \"FAILED\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "recastnavigation", + "text": "# recastnavigation 测试工程:用 Recast 烘一张导航网格,用 Detour 在上面\n# 走路,并对路径做断言。mcpp-index 自引用 workspace 的一员——workspace 根\n# 的 [indices] 重定向指向本仓库,所以 compat.recastnavigation 解析到签入的\n# 描述符(pkgs/c/compat.recastnavigation.lua)。\n#\n# 地面是 20x20、中间挖穿一个 4x4 洞的正方形,因此从 (2,2) 到 (18,18) 的\n# 路线不可能是一条直线段。「包坏了但测试依然绿」的情况有三种,这里逐项\n# 防住:\n#\n# * 栅格化出空网格 -> 对 nverts/npolys 做断言\n# * 网格是一整块大四方形 -> 直线路径必须拐弯,洞若被构建器悄悄填平,\n# 就在这里失败\n# * Detour 没被真正链接 -> findPath / findStraightPath 都被调用,\n# 缺库在链接期就失败,根本到不了这里\n#\n# 本成员只测核心构建(Recast + Detour);`crowd`、`tilecache`、\n# `debug-utils` 三个 feature 由 tests/examples/recastnavigation-features\n# 覆盖。\n[package]\nname = \"recastnavigation-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nrecastnavigation = \"1.6.0\"\n", + "files": [ + { + "path": "tests/examples/recastnavigation/tests/navmesh_path.cpp", + "text": "// compat.recastnavigation——烘一张导航网格,在上面走路,对路径做断言。\n//\n// 地面是 20x20、中间挖穿一个 4x4 洞的正方形,所以从 (2,2) 到 (18,18) 的\n// 路线不可能是一条直线段。「包坏了但测试依然绿」的情况有三种,每一种\n// 都被单独防住:\n//\n// * 网格栅格化成了空集 -> 对 nverts/npolys 做断言\n// * 网格是一整块大四方形 -> 直线路径必须拐弯,洞若被构建器悄悄填平,\n// 就在这里失败\n// * Detour 没被真正链接 -> findPath / findStraightPath 都被调用,\n// 缺库在链接期就失败,根本到不了这里\n//\n// 两种 include 拼写是有意混用的:Recast 走生成的 `recastnavigation/`\n// 前缀,Detour 走裸的 ``。上游的 CMake 安装把两条路径\n// 都放到接口 include path 上;若只有一种能用,shim 层就是没被测到的。\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nimport std;\n\nnamespace {\n\nconstexpr float kSize = 20.0f; // 地面边长(x 与 z)\nconstexpr float kHoleLo = 8.0f; // 洞在两根轴上都横跨 [8,12]\nconstexpr float kHoleHi = 12.0f;\n\nconstexpr float kAgentHeight = 2.0f;\nconstexpr float kAgentRadius = 0.6f;\nconstexpr float kAgentClimb = 0.9f;\n\nstruct Quad { float ax, az, bx, bz, cx, cz, dx, dz; };\n\n// 四个四边形拼成环形。俯视(沿 -y 轴往下看)时角点顺序为顺时针——这正是\n// rcCalcTriNormal 产出朝上法线的原因;rcMarkWalkableTriangles 测试的是\n// `norm[1] > threshold`,绕序反了不会报错,只会静默得到一张空网格。\nconstexpr Quad kQuads[] = {\n { 0, 0, 0, kHoleLo, kSize, kHoleLo, kSize, 0 }, // 南侧条带\n { 0, kHoleHi, 0, kSize, kSize, kSize, kSize, kHoleHi }, // 北侧条带\n { 0, kHoleLo, 0, kHoleHi, kHoleLo, kHoleHi, kHoleLo, kHoleLo }, // 西侧条带\n { kHoleHi, kHoleLo, kHoleHi, kHoleHi, kSize, kHoleHi, kSize, kHoleLo }, // 东侧条带\n};\n\nstruct FloorMesh {\n std::vector verts;\n std::vector tris;\n\n FloorMesh() {\n for (const Quad& q : kQuads) {\n const int base = static_cast(verts.size()) / 3;\n const float corners[4][2] = {\n { q.ax, q.az }, { q.bx, q.bz }, { q.cx, q.cz }, { q.dx, q.dz }\n };\n for (const auto& c : corners) {\n verts.push_back(c[0]);\n verts.push_back(0.0f);\n verts.push_back(c[1]);\n }\n tris.insert(tris.end(), { base + 0, base + 1, base + 2 });\n tris.insert(tris.end(), { base + 0, base + 2, base + 3 });\n }\n }\n\n int vertCount() const { return static_cast(verts.size()) / 3; }\n int triCount() const { return static_cast(tris.size()) / 3; }\n};\n\n} // namespace\n\nint main() {\n bool ok = true;\n const auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n const FloorMesh floor;\n std::println(\"geometry: {} verts, {} tris\", floor.vertCount(), floor.triCount());\n\n rcContext ctx;\n\n rcConfig cfg{};\n cfg.cs = 0.3f;\n cfg.ch = 0.2f;\n cfg.walkableSlopeAngle = 45.0f;\n cfg.walkableHeight = static_cast(std::ceil(kAgentHeight / cfg.ch));\n cfg.walkableClimb = static_cast(std::floor(kAgentClimb / cfg.ch));\n cfg.walkableRadius = static_cast(std::ceil(kAgentRadius / cfg.cs));\n cfg.maxEdgeLen = static_cast(12.0f / cfg.cs);\n cfg.maxSimplificationError = 1.3f;\n cfg.minRegionArea = static_cast(rcSqr(8.0f));\n cfg.mergeRegionArea = static_cast(rcSqr(20.0f));\n cfg.maxVertsPerPoly = 6;\n cfg.detailSampleDist = cfg.cs * 6.0f;\n cfg.detailSampleMaxError = cfg.ch * 1.0f;\n cfg.bmin[0] = 0.0f; cfg.bmin[1] = 0.0f; cfg.bmin[2] = 0.0f;\n cfg.bmax[0] = kSize; cfg.bmax[1] = 0.0f; cfg.bmax[2] = kSize;\n rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);\n std::println(\"grid: {}x{}\", cfg.width, cfg.height);\n\n // ── Recast:原始三角形 -> 多边形网格 ──────────────────────────────────\n rcHeightfield* solid = rcAllocHeightfield();\n check(solid != nullptr, \"rcAllocHeightfield\");\n check(rcCreateHeightfield(&ctx, *solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch),\n \"rcCreateHeightfield\");\n\n std::vector triareas(static_cast(floor.triCount()), 0);\n rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, floor.verts.data(), floor.vertCount(),\n floor.tris.data(), floor.triCount(), triareas.data());\n const int walkableTris = static_cast(std::ranges::count(triareas, RC_WALKABLE_AREA));\n std::println(\"walkable triangles: {} / {}\", walkableTris, floor.triCount());\n check(walkableTris == floor.triCount(), \"every floor triangle is walkable (winding gives +y normals)\");\n\n check(rcRasterizeTriangles(&ctx, floor.verts.data(), floor.vertCount(), floor.tris.data(),\n triareas.data(), floor.triCount(), *solid, cfg.walkableClimb),\n \"rcRasterizeTriangles\");\n rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *solid);\n rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *solid);\n rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *solid);\n\n rcCompactHeightfield* chf = rcAllocCompactHeightfield();\n check(rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *solid, *chf),\n \"rcBuildCompactHeightfield\");\n rcFreeHeightField(solid);\n\n check(rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf), \"rcErodeWalkableArea\");\n check(rcBuildDistanceField(&ctx, *chf), \"rcBuildDistanceField\");\n check(rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea), \"rcBuildRegions\");\n\n rcContourSet* cset = rcAllocContourSet();\n check(rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset), \"rcBuildContours\");\n\n rcPolyMesh* pmesh = rcAllocPolyMesh();\n check(rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *pmesh), \"rcBuildPolyMesh\");\n std::println(\"poly mesh: {} verts, {} polys\", pmesh->nverts, pmesh->npolys);\n check(pmesh->nverts > 0, \"Recast produced vertices\");\n check(pmesh->npolys > 0, \"Recast produced polygons\");\n\n rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail();\n check(rcBuildPolyMeshDetail(&ctx, *pmesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dmesh),\n \"rcBuildPolyMeshDetail\");\n rcFreeCompactHeightfield(chf);\n rcFreeContourSet(cset);\n\n // ── Detour:多边形网格 -> 可查询的导航网格 ────────────────────────────\n // Detour 只会走带查询过滤器所含 flag 的多边形。\n for (int i = 0; i < pmesh->npolys; ++i)\n pmesh->flags[i] = (pmesh->areas[i] == RC_WALKABLE_AREA) ? 1 : 0;\n\n dtNavMeshCreateParams params{};\n params.verts = pmesh->verts;\n params.vertCount = pmesh->nverts;\n params.polys = pmesh->polys;\n params.polyFlags = pmesh->flags;\n params.polyAreas = pmesh->areas;\n params.polyCount = pmesh->npolys;\n params.nvp = pmesh->nvp;\n params.detailMeshes = dmesh->meshes;\n params.detailVerts = dmesh->verts;\n params.detailVertsCount = dmesh->nverts;\n params.detailTris = dmesh->tris;\n params.detailTriCount = dmesh->ntris;\n params.walkableHeight = kAgentHeight;\n params.walkableRadius = kAgentRadius;\n params.walkableClimb = kAgentClimb;\n rcVcopy(params.bmin, pmesh->bmin);\n rcVcopy(params.bmax, pmesh->bmax);\n params.cs = cfg.cs;\n params.ch = cfg.ch;\n params.buildBvTree = true;\n\n unsigned char* navData = nullptr;\n int navDataSize = 0;\n check(dtCreateNavMeshData(¶ms, &navData, &navDataSize), \"dtCreateNavMeshData\");\n\n dtNavMesh* nav = dtAllocNavMesh();\n check(nav != nullptr, \"dtAllocNavMesh\");\n check(dtStatusSucceed(nav->init(navData, navDataSize, DT_TILE_FREE_DATA)), \"dtNavMesh::init\");\n\n dtNavMeshQuery* query = dtAllocNavMeshQuery();\n check(query != nullptr, \"dtAllocNavMeshQuery\");\n check(dtStatusSucceed(query->init(nav, 2048)), \"dtNavMeshQuery::init\");\n\n dtQueryFilter filter;\n filter.setIncludeFlags(1);\n filter.setExcludeFlags(0);\n\n const float ext[3] = { 2.0f, 4.0f, 2.0f };\n const float start[3] = { 2.0f, 0.0f, 2.0f };\n const float goal[3] = { 18.0f, 0.0f, 18.0f };\n\n dtPolyRef startRef = 0, endRef = 0;\n float startNear[3] = { 0, 0, 0 }, endNear[3] = { 0, 0, 0 };\n check(dtStatusSucceed(query->findNearestPoly(start, ext, &filter, &startRef, startNear)), \"findNearestPoly(start)\");\n check(dtStatusSucceed(query->findNearestPoly(goal, ext, &filter, &endRef, endNear)), \"findNearestPoly(goal)\");\n check(startRef != 0, \"start polygon found\");\n check(endRef != 0, \"goal polygon found\");\n\n std::vector corridor(256);\n int corridorLen = 0;\n check(dtStatusSucceed(query->findPath(startRef, endRef, startNear, endNear, &filter,\n corridor.data(), &corridorLen, static_cast(corridor.size()))),\n \"findPath\");\n std::println(\"path corridor: {} polys\", corridorLen);\n check(corridorLen > 0, \"findPath returned a corridor\");\n\n std::vector straight(256 * 3);\n std::vector straightFlags(256);\n std::vector straightRefs(256);\n int straightLen = 0;\n check(dtStatusSucceed(query->findStraightPath(startNear, endNear, corridor.data(), corridorLen,\n straight.data(), straightFlags.data(), straightRefs.data(),\n &straightLen, 256)),\n \"findStraightPath\");\n std::println(\"straight path: {} points\", straightLen);\n\n check(straightLen >= 2, \"straight path has a start and an end\");\n // 洞挡住了直对角线,所以最短路径必须拐弯。只有两个点意味着查询笔直地\n // 穿过了洞——即洞没进网格,或者网格是一整块大四方形。\n check(straightLen >= 3, \"path bends around the hole (>= 3 straight points)\");\n\n if (straightLen >= 2) {\n const float* last = &straight[(straightLen - 1) * 3];\n const float gap = dtVdist(last, goal);\n std::println(\"endpoint distance to goal: {:.3f}\", gap);\n check(gap < 2.0f, \"path ends at the goal\");\n\n float walked = 0.0f;\n for (int i = 1; i < straightLen; ++i)\n walked += dtVdist(&straight[(i - 1) * 3], &straight[i * 3]);\n const float direct = dtVdist(start, goal);\n std::println(\"path length {:.3f} vs direct {:.3f}\", walked, direct);\n check(walked > direct + 0.5f, \"path is longer than the direct line (it went around)\");\n }\n\n dtFreeNavMeshQuery(query);\n dtFreeNavMesh(nav);\n rcFreePolyMesh(pmesh);\n rcFreePolyMeshDetail(dmesh);\n\n std::println(\"compat.recastnavigation smoke test: {}\", ok ? \"ok\" : \"FAILED\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "redis-plus-plus-async", + "text": "# redis-plus-plus ASYNC test project: consumes compat.redis-plus-plus with the\n# `async` feature (which pulls compat.libuv) and asserts the libuv-backed async\n# interface under `mcpp test`. Part of the mcpp-index self-referential\n# workspace — the workspace-root `[indices]` redirect points at this repo.\n#\n# The test is entirely OFFLINE: same minimal RESP server on loopback sockets as\n# the sync members, driven through sw::redis::AsyncRedis (which runs the libuv\n# event loop on a background thread). PING + SET are fired back-to-back, so the\n# server parses real RESP arrays — the async client may pipeline both commands\n# into one TCP segment.\n[package]\nname = \"redis-plus-plus-async-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nredis-plus-plus = { version = \"1.3.13\", features = [\"async\"] }\n", + "files": [ + { + "path": "tests/examples/redis-plus-plus-async/tests/redis_test.cpp", + "text": "// redis_test.cpp — offline behavioral test for the `async` feature of\n// compat.redis-plus-plus (libuv-backed AsyncRedis).\n//\n// No redis-server binary and no network access: a minimal RESP server runs\n// inside the process (raw loopback sockets, same platform abstraction as the\n// websocket member) and the sw::redis::AsyncRedis client is driven against it.\n// The async client can pipeline PING + SET into a single TCP segment, so the\n// server parses complete RESP arrays instead of scanning for keywords.\n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n// ── Platform socket abstraction (same as tests/examples/websocket) ─────────\n\n#ifdef _WIN32\n#include \n#include \nusing SockType = SOCKET;\nconstexpr SockType kInvalidSocket = INVALID_SOCKET;\nusing AddrLenType = int;\n#else\n#include \n#include \n#include \n#include \n#include \nusing SockType = int;\nconstexpr SockType kInvalidSocket = -1;\nusing AddrLenType = socklen_t;\n#endif\n\nvoid init_sockets()\n{\n#ifdef _WIN32\n WSADATA wsa;\n WSAStartup(MAKEWORD(2, 2), &wsa);\n#endif\n}\n\nvoid close_socket(SockType s)\n{\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n}\n\nvoid set_recv_timeout(SockType s, int ms)\n{\n#ifdef _WIN32\n DWORD t = static_cast(ms);\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&t), sizeof(t));\n#else\n timeval tv{};\n tv.tv_sec = ms / 1000;\n tv.tv_usec = (ms % 1000) * 1000;\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));\n#endif\n}\n\n// ── Minimal RESP server: PING -> +PONG, SET -> +OK ─────────────────────────\n// Parses complete RESP arrays so pipelined commands (async client) are served\n// one reply each instead of one reply per recv().\nclass RespServer\n{\npublic:\n ~RespServer()\n {\n if (client != kInvalidSocket) close_socket(client);\n if (listener != kInvalidSocket) close_socket(listener);\n if (thread.joinable()) thread.join();\n }\n\n bool start()\n {\n init_sockets();\n listener = socket(AF_INET, SOCK_STREAM, 0);\n if (listener == kInvalidSocket) return false;\n int one = 1;\n#ifdef _WIN32\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,\n reinterpret_cast(&one), sizeof(one));\n#else\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));\n#endif\n sockaddr_in addr{};\n addr.sin_family = AF_INET;\n addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n addr.sin_port = 0; // ephemeral\n if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0) return false;\n AddrLenType alen = sizeof(addr);\n getsockname(listener, reinterpret_cast(&addr), &alen);\n port = ntohs(addr.sin_port);\n if (listen(listener, 1) != 0) return false;\n thread = std::thread([this] { run(); });\n return true;\n }\n\n int port = 0;\n\nprivate:\n void run()\n {\n SockType s = accept(listener, nullptr, nullptr);\n if (s == kInvalidSocket) return;\n client = s;\n set_recv_timeout(client, 5000);\n\n std::string buf;\n int served = 0;\n while (served < 16) {\n char tmp[1024];\n const int n = static_cast(recv(client, tmp, sizeof(tmp), 0));\n if (n <= 0) break;\n buf.append(tmp, static_cast(n));\n\n size_t off = 0;\n while (served < 16) {\n // `*\\r\\n` then `` bulk strings `$\\r\\n\\r\\n`.\n if (buf.size() < off + 3 || buf[off] != '*') break;\n const size_t nl = buf.find(\"\\r\\n\", off);\n if (nl == std::string::npos) break;\n const int count = std::atoi(buf.substr(off + 1, nl - off - 1).c_str());\n off = nl + 2;\n std::string cmd;\n bool complete = true;\n for (int i = 0; i < count; ++i) {\n if (buf.size() < off + 1 || buf[off] != '$') { complete = false; break; }\n const size_t nl2 = buf.find(\"\\r\\n\", off);\n if (nl2 == std::string::npos) { complete = false; break; }\n const int len = std::atoi(buf.substr(off + 1, nl2 - off - 1).c_str());\n off = nl2 + 2;\n if (buf.size() < off + static_cast(len) + 2) { complete = false; break; }\n if (i == 0) cmd.assign(buf, off, static_cast(len));\n off += static_cast(len) + 2;\n }\n if (!complete) break;\n buf.erase(0, off);\n off = 0;\n\n if (cmd == \"PING\") {\n static constexpr char kPong[] = \"+PONG\\r\\n\";\n send(client, kPong, static_cast(sizeof(kPong) - 1), 0);\n } else if (cmd == \"SET\") {\n static constexpr char kOk[] = \"+OK\\r\\n\";\n send(client, kOk, static_cast(sizeof(kOk) - 1), 0);\n }\n ++served;\n }\n }\n }\n\n SockType listener = kInvalidSocket;\n SockType client = kInvalidSocket;\n std::thread thread;\n};\n\nint main()\n{\n RespServer server;\n if (!server.start()) {\n std::cerr << \"failed to start RESP server\\n\";\n return 1;\n }\n\n sw::redis::ConnectionOptions opts;\n opts.host = \"127.0.0.1\";\n opts.port = server.port;\n opts.connect_timeout = std::chrono::milliseconds(3000);\n opts.socket_timeout = std::chrono::milliseconds(3000);\n\n sw::redis::ConnectionPoolOptions pool_opts;\n pool_opts.size = 1;\n\n try {\n sw::redis::AsyncRedis redis(opts, pool_opts);\n\n // Fire both commands back-to-back; the libuv loop runs on a background\n // thread and the server may see them pipelined in one segment.\n auto ping_res = redis.ping();\n auto set_res = redis.set(\"mcpp\", \"async\");\n\n const std::string pong = ping_res.get();\n if (pong != \"PONG\") {\n std::cerr << \"unexpected async PING reply: '\" << pong << \"'\\n\";\n return 2;\n }\n if (!set_res.get()) {\n std::cerr << \"async SET did not return OK\\n\";\n return 3;\n }\n\n std::cout << \"OK: async PING -> \" << pong << \", SET -> OK\\n\";\n return 0;\n } catch (const sw::redis::Error &e) {\n std::cerr << \"redis error: \" << e.what() << \"\\n\";\n return 4;\n }\n}\n" + } + ] + }, + { + "project": "redis-plus-plus-v133", + "text": "# redis-plus-plus v1.3.3 test project — the PRE-watershed structure: 15 sync\n# TUs (no redis_uri.cpp / patterns/redlock.cpp, no hiredis_features.h, flat\n# no_tls/tls.h). This is the member that proves the single-package union holds:\n# at 1.3.3 exactly two source globs match nothing (a warning, not an error) and\n# the build must still link and pass the same offline RESP assertions as the\n# 1.3.13 member (tests/examples/redis-plus-plus).\n#\n# The `compat` index redirect is inherited from the workspace root.\n# Part of the mcpp-index self-referential workspace.\n[package]\nname = \"redis-plus-plus-v133-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nredis-plus-plus = \"1.3.3\"\n\n# 1.3.3's utils.h uses uint16_t without including (fixed upstream in\n# 1.3.6+); under the vendored gcc/libstdc++ sysroot the test TU (which includes\n# first) needs the same force-include the package uses.\n[build]\ncxxflags = [\"-include\", \"cstdint\"]\n", + "files": [ + { + "path": "tests/examples/redis-plus-plus-v133/tests/redis_test.cpp", + "text": "// redis_test.cpp — offline behavioral test for compat.redis-plus-plus.\n//\n// No redis-server binary and no network access: this test runs a minimal RESP\n// server inside the process (raw loopback sockets, same platform abstraction\n// as the websocket member) and drives the sw::redis::Redis sync client against\n// it. That exercises the full stack the package must get right:\n// * both static libs actually link (redis++ + hiredis),\n// * hiredis connects and speaks the RESP protocol (PING -> +PONG),\n// * redis++ formats commands (SET -> +OK) and parses replies back to C++.\n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n// ── Platform socket abstraction (same as tests/examples/websocket) ─────────\n\n#ifdef _WIN32\n#include \n#include \nusing SockType = SOCKET;\nconstexpr SockType kInvalidSocket = INVALID_SOCKET;\nusing AddrLenType = int;\n#else\n#include \n#include \n#include \n#include \n#include \nusing SockType = int;\nconstexpr SockType kInvalidSocket = -1;\nusing AddrLenType = socklen_t;\n#endif\n\nvoid init_sockets()\n{\n#ifdef _WIN32\n WSADATA wsa;\n WSAStartup(MAKEWORD(2, 2), &wsa);\n#endif\n}\n\nvoid close_socket(SockType s)\n{\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n}\n\n// Safety net: any read that stalls this long makes the server thread return so\n// the test fails cleanly instead of hanging the process (and, on CI, a runner).\nvoid set_recv_timeout(SockType s, int ms)\n{\n#ifdef _WIN32\n DWORD t = static_cast(ms);\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&t), sizeof(t));\n#else\n timeval tv{};\n tv.tv_sec = ms / 1000;\n tv.tv_usec = (ms % 1000) * 1000;\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));\n#endif\n}\n\n// ── Minimal RESP server: answers PING with +PONG, SET with +OK ─────────────\n\nclass RespServer\n{\npublic:\n ~RespServer()\n {\n if (client != kInvalidSocket) close_socket(client);\n if (listener != kInvalidSocket) close_socket(listener);\n if (thread.joinable()) thread.join();\n }\n\n bool start()\n {\n init_sockets();\n listener = socket(AF_INET, SOCK_STREAM, 0);\n if (listener == kInvalidSocket) return false;\n int one = 1;\n#ifdef _WIN32\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,\n reinterpret_cast(&one), sizeof(one));\n#else\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));\n#endif\n sockaddr_in addr{};\n addr.sin_family = AF_INET;\n addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n addr.sin_port = 0; // ephemeral\n if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0) return false;\n AddrLenType alen = sizeof(addr);\n getsockname(listener, reinterpret_cast(&addr), &alen);\n port = ntohs(addr.sin_port);\n if (listen(listener, 1) != 0) return false;\n thread = std::thread([this] { run(); });\n return true;\n }\n\n int port = 0;\n\nprivate:\n void run()\n {\n SockType s = accept(listener, nullptr, nullptr);\n if (s == kInvalidSocket) return;\n client = s;\n set_recv_timeout(client, 5000);\n\n char buf[1024];\n int served = 0;\n // The client keeps the connection open for the whole test; serve a\n // handful of commands, then stop so the client's next read fails\n // cleanly instead of hanging.\n while (served < 8) {\n const int n = static_cast(recv(client, buf, sizeof(buf) - 1, 0));\n if (n <= 0) break;\n const std::string_view req(buf, static_cast(n));\n if (req.find(\"PING\") != std::string_view::npos) {\n static constexpr char kPong[] = \"+PONG\\r\\n\";\n send(client, kPong, static_cast(sizeof(kPong) - 1), 0);\n ++served;\n } else if (req.find(\"SET\") != std::string_view::npos) {\n static constexpr char kOk[] = \"+OK\\r\\n\";\n send(client, kOk, static_cast(sizeof(kOk) - 1), 0);\n ++served;\n }\n }\n }\n\n SockType listener = kInvalidSocket;\n SockType client = kInvalidSocket;\n std::thread thread;\n};\n\nint main()\n{\n RespServer server;\n if (!server.start()) {\n std::cerr << \"failed to start RESP server\\n\";\n return 1;\n }\n\n sw::redis::ConnectionOptions opts;\n opts.host = \"127.0.0.1\";\n opts.port = server.port;\n opts.connect_timeout = std::chrono::milliseconds(3000);\n opts.socket_timeout = std::chrono::milliseconds(3000);\n\n try {\n sw::redis::Redis redis(opts);\n\n const std::string pong = redis.ping();\n if (pong != \"PONG\") {\n std::cerr << \"unexpected PING reply: '\" << pong << \"'\\n\";\n return 2;\n }\n\n if (!redis.set(\"mcpp\", \"redis-plus-plus\")) {\n std::cerr << \"SET did not return OK\\n\";\n return 3;\n }\n\n std::cout << \"OK: PING -> \" << pong << \", SET -> OK\\n\";\n return 0;\n } catch (const sw::redis::Error &e) {\n std::cerr << \"redis error: \" << e.what() << \"\\n\";\n return 4;\n }\n}\n" + } + ] + }, + { + "project": "redis-plus-plus", + "text": "# redis-plus-plus test project: consumes compat.redis-plus-plus (and through it\n# compat.hiredis) and asserts behavior under `mcpp test`. Part of the\n# mcpp-index self-referential workspace — the workspace-root `[indices]`\n# redirect points at this repo, so the dependency resolves to the checked-in\n# recipe (pkgs/c/compat.redis-plus-plus.lua).\n#\n# The test is entirely OFFLINE and self-contained: it runs a minimal RESP\n# PING/SET server inside the test process (raw loopback sockets, same pattern\n# as the websocket member) and drives the sw::redis::Redis sync client against\n# it — a full protocol round trip with no redis-server binary and no network\n# access.\n[package]\nname = \"redis-plus-plus-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nredis-plus-plus = \"1.3.13\"\n", + "files": [ + { + "path": "tests/examples/redis-plus-plus/tests/redis_test.cpp", + "text": "// redis_test.cpp — offline behavioral test for compat.redis-plus-plus.\n//\n// No redis-server binary and no network access: this test runs a minimal RESP\n// server inside the process (raw loopback sockets, same platform abstraction\n// as the websocket member) and drives the sw::redis::Redis sync client against\n// it. That exercises the full stack the package must get right:\n// * both static libs actually link (redis++ + hiredis),\n// * hiredis connects and speaks the RESP protocol (PING -> +PONG),\n// * redis++ formats commands (SET -> +OK) and parses replies back to C++.\n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n// ── Platform socket abstraction (same as tests/examples/websocket) ─────────\n\n#ifdef _WIN32\n#include \n#include \nusing SockType = SOCKET;\nconstexpr SockType kInvalidSocket = INVALID_SOCKET;\nusing AddrLenType = int;\n#else\n#include \n#include \n#include \n#include \n#include \nusing SockType = int;\nconstexpr SockType kInvalidSocket = -1;\nusing AddrLenType = socklen_t;\n#endif\n\nvoid init_sockets()\n{\n#ifdef _WIN32\n WSADATA wsa;\n WSAStartup(MAKEWORD(2, 2), &wsa);\n#endif\n}\n\nvoid close_socket(SockType s)\n{\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n}\n\n// Safety net: any read that stalls this long makes the server thread return so\n// the test fails cleanly instead of hanging the process (and, on CI, a runner).\nvoid set_recv_timeout(SockType s, int ms)\n{\n#ifdef _WIN32\n DWORD t = static_cast(ms);\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&t), sizeof(t));\n#else\n timeval tv{};\n tv.tv_sec = ms / 1000;\n tv.tv_usec = (ms % 1000) * 1000;\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));\n#endif\n}\n\n// ── Minimal RESP server: answers PING with +PONG, SET with +OK ─────────────\n\nclass RespServer\n{\npublic:\n ~RespServer()\n {\n if (client != kInvalidSocket) close_socket(client);\n if (listener != kInvalidSocket) close_socket(listener);\n if (thread.joinable()) thread.join();\n }\n\n bool start()\n {\n init_sockets();\n listener = socket(AF_INET, SOCK_STREAM, 0);\n if (listener == kInvalidSocket) return false;\n int one = 1;\n#ifdef _WIN32\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,\n reinterpret_cast(&one), sizeof(one));\n#else\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));\n#endif\n sockaddr_in addr{};\n addr.sin_family = AF_INET;\n addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n addr.sin_port = 0; // ephemeral\n if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0) return false;\n AddrLenType alen = sizeof(addr);\n getsockname(listener, reinterpret_cast(&addr), &alen);\n port = ntohs(addr.sin_port);\n if (listen(listener, 1) != 0) return false;\n thread = std::thread([this] { run(); });\n return true;\n }\n\n int port = 0;\n\nprivate:\n void run()\n {\n SockType s = accept(listener, nullptr, nullptr);\n if (s == kInvalidSocket) return;\n client = s;\n set_recv_timeout(client, 5000);\n\n char buf[1024];\n int served = 0;\n // The client keeps the connection open for the whole test; serve a\n // handful of commands, then stop so the client's next read fails\n // cleanly instead of hanging.\n while (served < 8) {\n const int n = static_cast(recv(client, buf, sizeof(buf) - 1, 0));\n if (n <= 0) break;\n const std::string_view req(buf, static_cast(n));\n if (req.find(\"PING\") != std::string_view::npos) {\n static constexpr char kPong[] = \"+PONG\\r\\n\";\n send(client, kPong, static_cast(sizeof(kPong) - 1), 0);\n ++served;\n } else if (req.find(\"SET\") != std::string_view::npos) {\n static constexpr char kOk[] = \"+OK\\r\\n\";\n send(client, kOk, static_cast(sizeof(kOk) - 1), 0);\n ++served;\n }\n }\n }\n\n SockType listener = kInvalidSocket;\n SockType client = kInvalidSocket;\n std::thread thread;\n};\n\nint main()\n{\n RespServer server;\n if (!server.start()) {\n std::cerr << \"failed to start RESP server\\n\";\n return 1;\n }\n\n sw::redis::ConnectionOptions opts;\n opts.host = \"127.0.0.1\";\n opts.port = server.port;\n opts.connect_timeout = std::chrono::milliseconds(3000);\n opts.socket_timeout = std::chrono::milliseconds(3000);\n\n try {\n sw::redis::Redis redis(opts);\n\n const std::string pong = redis.ping();\n if (pong != \"PONG\") {\n std::cerr << \"unexpected PING reply: '\" << pong << \"'\\n\";\n return 2;\n }\n\n if (!redis.set(\"mcpp\", \"redis-plus-plus\")) {\n std::cerr << \"SET did not return OK\\n\";\n return 3;\n }\n\n std::cout << \"OK: PING -> \" << pong << \", SET -> OK\\n\";\n return 0;\n } catch (const sw::redis::Error &e) {\n std::cerr << \"redis error: \" << e.what() << \"\\n\";\n return 4;\n }\n}\n" + } + ] + }, + { + "project": "reflectcpp", + "text": "# reflect-cpp test project: consumes compat.reflectcpp from this repo's own index\n# and asserts struct <-> JSON round trips through the bundled yyjson.\n[package]\nname = \"reflectcpp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nreflectcpp = \"0.25.0\"\n", + "files": [ + { + "path": "tests/examples/reflectcpp/tests/json_roundtrip.cpp", + "text": "// Behavioral test: struct -> JSON -> struct, through the JSON backend.\n//\n// The package compiles two umbrella TUs plus the vendored yyjson. All three have\n// to be present and agree: leave out src/yyjson.c and this fails to LINK; get the\n// include roots wrong and rfl/json/save.hpp picks a different yyjson than the one\n// compiled, which fails at runtime rather than at build time. So the assertions\n// go all the way to parsed values.\n#include \n#include \n\n#include \n#include \n#include \n#include \n\nstruct Point {\n int x;\n int y;\n};\n\nstruct Document {\n std::string name;\n int version;\n bool enabled;\n std::vector points;\n std::optional note;\n};\n\nint main() {\n const Document doc{\n .name = \"mcpp\",\n .version = 23,\n .enabled = true,\n .points = {{1, 2}, {-3, 4}},\n .note = std::nullopt,\n };\n\n // --- round trip ---\n const std::string json = rfl::json::write(doc);\n assert(!json.empty());\n\n const auto parsed = rfl::json::read(json);\n assert(parsed && \"a document this library just wrote must parse\");\n const Document& back = parsed.value();\n assert(back.name == doc.name);\n assert(back.version == doc.version);\n assert(back.enabled == doc.enabled);\n assert(back.points.size() == 2);\n assert(back.points[0].x == 1 && back.points[0].y == 2);\n assert(back.points[1].x == -3 && back.points[1].y == 4);\n assert(!back.note.has_value());\n\n // --- the field-name rename processor, which is why callers reach for rfl ---\n const std::string camel = rfl::json::write(doc);\n const auto camel_back = rfl::json::read(camel);\n assert(camel_back && camel_back.value().version == doc.version);\n\n // --- a malformed document must fail, not throw or crash ---\n const auto bad = rfl::json::read(R\"({\"name\": 5})\");\n assert(!bad && \"a type mismatch must surface as an error, not a value\");\n\n // --- to_generic / from_generic, the path an RPC layer uses when the\n // concrete type is only known per method ---\n const auto generic = rfl::to_generic(doc);\n const auto from_generic = rfl::from_generic(generic);\n assert(from_generic && from_generic.value().name == doc.name);\n\n // --- the JSON Schema generator ---\n const std::string schema = rfl::json::to_schema();\n assert(schema.find(\"version\") != std::string::npos);\n\n return 0;\n}\n" + } + ] + }, + { + "project": "sdl2", + "text": "[package]\nname = \"sdl2-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nsdl2 = \"2.32.10\"\n", + "files": [ + { + "path": "tests/examples/sdl2/tests/video.cpp", + "text": "// Behavioral test — verify compat.sdl2 builds an SDL that can actually run a\n// video subsystem, create a window and pump events.\n//\n// SDL is the one backend in this family that CAN be exercised headlessly:\n// its `dummy` video driver is a real, fully functional driver that just never\n// touches a display. So unlike the OpenGL and Vulkan backends, this is not a\n// link-only smoke test — SDL_Init, SDL_CreateWindow, SDL_GetWindowSize and the\n// event pump all execute.\n//\n// The driver is selected through SDL_HINT_VIDEODRIVER rather than the\n// SDL_VIDEODRIVER environment variable so the test is self-contained and does\n// not depend on how CI invokes it.\n// SDL_MAIN_HANDLED before : on Windows SDL_main.h does\n// `#define main SDL_main` and expects the real entry point to come from\n// SDL2main (src/main/windows/SDL_windows_main.c). This package does not ship\n// that — a library consumer should not be handed an entry point — so the test\n// takes SDL's documented alternative and keeps its own main, pairing it with\n// SDL_SetMainReady() below. Without this the link fails with\n// \"LNK1561: entry point must be defined\".\n#define SDL_MAIN_HANDLED\n#include \nimport std;\n\nint main() {\n SDL_SetMainReady();\n SDL_SetHint(SDL_HINT_VIDEODRIVER, \"dummy\");\n\n if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {\n std::println(\"SDL_Init failed: {}\", SDL_GetError());\n return 1;\n }\n\n // A build whose video subsystem never registered a driver fails here, which\n // is the failure mode a bad SDL_config.h produces — upstream's Linux\n // fallback config (SDL_config_minimal.h) builds exactly that.\n const char* driver = SDL_GetCurrentVideoDriver();\n if (driver == nullptr) {\n std::println(\"no video driver active: {}\", SDL_GetError());\n SDL_Quit();\n return 2;\n }\n\n SDL_Window* window = SDL_CreateWindow(\n \"compat.sdl2\", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,\n 320, 240, SDL_WINDOW_HIDDEN);\n if (window == nullptr) {\n std::println(\"SDL_CreateWindow failed: {}\", SDL_GetError());\n SDL_Quit();\n return 3;\n }\n\n int width = 0;\n int height = 0;\n SDL_GetWindowSize(window, &width, &height);\n if (width != 320 || height != 240) {\n std::println(\"unexpected window size: {}x{}\", width, height);\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 4;\n }\n\n // Pumping events exercises the event subsystem end to end; SDL_QUIT is\n // pushed and read back rather than waited for, so nothing here can hang.\n SDL_Event pushed{};\n pushed.type = SDL_QUIT;\n if (SDL_PushEvent(&pushed) < 0) {\n std::println(\"SDL_PushEvent failed: {}\", SDL_GetError());\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 5;\n }\n SDL_PumpEvents();\n\n bool sawQuit = false;\n SDL_Event event{};\n while (SDL_PollEvent(&event) != 0) {\n if (event.type == SDL_QUIT) sawQuit = true;\n }\n if (!sawQuit) {\n std::println(\"pushed SDL_QUIT never came back out of the event queue\");\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 6;\n }\n\n SDL_version linked{};\n SDL_GetVersion(&linked);\n\n std::println(\"compat.sdl2: ok (SDL {}.{}.{}, driver={}, {}x{} window, events round-tripped)\",\n linked.major, linked.minor, linked.patch, driver, width, height);\n\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 0;\n}\n" + } + ] + }, + { + "project": "sdl3", + "text": "# compat.sdl3 test project. `compat` redirect inherited from the workspace root.\n#\n# Everything asserted runs on a headless runner: the test forces SDL's DUMMY\n# video driver, so it exercises the real video subsystem — window creation,\n# surface ops, the event queue — without needing a display server on any of the\n# three platforms.\n[package]\nname = \"sdl3-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nsdl3 = \"3.4.2\"\n", + "files": [ + { + "path": "tests/examples/sdl3/tests/init.cpp", + "text": "// Behavioral test — compat.sdl3 builds a working SDL3, not just headers.\n//\n// SDL is a library where \"it compiled\" is especially weak evidence: most of it\n// is backends selected by a config header, and a wrong config produces a\n// library that links and then has no video driver at all. So the assertions\n// below are about what the BUILD CONFIG decided:\n//\n// * the runtime version matches the headers (one tarball, not two)\n// * the compiled-in video driver list contains dummy AND offscreen, which\n// are exactly what a config that fell through to SDL_build_config_minimal.h\n// would NOT have\n// * the dummy driver actually initialises, creates a window, and gives back\n// a surface of the requested size\n// * the timer subsystem advances\n//\n// No `import std;` — SDL's headers are textual C over the standard headers.\n// SDL_MAIN_HANDLED + SDL_main.h: this test brings its own main(), so SDL must\n// not rename it. SDL_SetMainReady() is declared in SDL_main.h, not SDL.h.\n#define SDL_MAIN_HANDLED\n#include \n#include \n\n#include \n#include \n\nint main() {\n // Header/runtime agreement. SDL_VERSION is the compile-time constant from\n // the headers; SDL_GetVersion() is what the linked objects report.\n const int compiled = SDL_VERSION;\n const int linked = SDL_GetVersion();\n if (compiled != linked) {\n std::printf(\"header says %d, library says %d — two different SDLs\\n\", compiled, linked);\n return 1;\n }\n if (linked != SDL_VERSIONNUM(3, 4, 2)) {\n std::printf(\"SDL version is %d, expected 3.4.2\\n\", linked);\n return 2;\n }\n\n SDL_SetMainReady();\n\n // The compiled-in driver list. A minimal config has none of these.\n const int drivers = SDL_GetNumVideoDrivers();\n if (drivers <= 0) {\n std::printf(\"no video drivers compiled in — the build config did not take\\n\");\n return 3;\n }\n bool haveDummy = false, haveOffscreen = false;\n for (int i = 0; i < drivers; ++i) {\n const char* name = SDL_GetVideoDriver(i);\n if (!name) continue;\n if (std::strcmp(name, \"dummy\") == 0) haveDummy = true;\n if (std::strcmp(name, \"offscreen\") == 0) haveOffscreen = true;\n }\n if (!haveDummy || !haveOffscreen) {\n std::printf(\"driver list is missing dummy/offscreen (%d drivers):\", drivers);\n for (int i = 0; i < drivers; ++i) std::printf(\" %s\", SDL_GetVideoDriver(i));\n std::printf(\"\\n\");\n return 4;\n }\n\n // Force dummy so this works with no display on any platform.\n SDL_SetHint(SDL_HINT_VIDEO_DRIVER, \"dummy\");\n if (!SDL_Init(SDL_INIT_VIDEO)) {\n std::printf(\"SDL_Init(VIDEO) failed: %s\\n\", SDL_GetError());\n return 5;\n }\n\n const char* current = SDL_GetCurrentVideoDriver();\n if (!current || std::strcmp(current, \"dummy\") != 0) {\n std::printf(\"current video driver is %s, expected dummy\\n\", current ? current : \"(null)\");\n SDL_Quit();\n return 6;\n }\n\n SDL_Window* window = SDL_CreateWindow(\"mcpp-index sdl3\", 320, 240, 0);\n if (!window) {\n std::printf(\"SDL_CreateWindow failed: %s\\n\", SDL_GetError());\n SDL_Quit();\n return 7;\n }\n int w = 0, h = 0;\n SDL_GetWindowSize(window, &w, &h);\n if (w != 320 || h != 240) {\n std::printf(\"window is %dx%d, asked for 320x240\\n\", w, h);\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 8;\n }\n\n // The dummy driver still backs a window with a real surface, so this\n // exercises the surface/pixel code rather than only the window bookkeeping.\n SDL_Surface* surface = SDL_GetWindowSurface(window);\n if (!surface || surface->w != 320 || surface->h != 240) {\n std::printf(\"window surface missing or wrong size: %s\\n\", SDL_GetError());\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 9;\n }\n if (!SDL_FillSurfaceRect(surface, nullptr, SDL_MapSurfaceRGB(surface, 0, 0, 0))) {\n std::printf(\"SDL_FillSurfaceRect failed: %s\\n\", SDL_GetError());\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 10;\n }\n\n // Timer subsystem: SDL_GetTicks must advance across a delay.\n const Uint64 before = SDL_GetTicks();\n SDL_Delay(20);\n const Uint64 after = SDL_GetTicks();\n if (after < before + 10) {\n std::printf(\"SDL_GetTicks did not advance (%llu -> %llu)\\n\",\n (unsigned long long)before, (unsigned long long)after);\n SDL_DestroyWindow(window);\n SDL_Quit();\n return 11;\n }\n\n SDL_DestroyWindow(window);\n SDL_Quit();\n\n std::printf(\"compat.sdl3: ok (version 3.4.2, %d video drivers, dummy window 320x240, timer advances)\\n\",\n drivers);\n return 0;\n}\n" + } + ] + }, + { + "project": "spdlog-compiled", + "text": "# spdlog test project: consumes compat.spdlog in its COMPILED mode\n# (`features = [\"compiled\"]`) — spdlog's src/*.cpp are compiled into the lib and\n# SPDLOG_COMPILED_LIB reaches this consumer's TUs as an interface define, so the\n# headers take the non-inline path and link against those objects.\n#\n# Sibling of tests/examples/spdlog/ (header-only default mode). Both modes ship\n# from one descriptor; this member is what keeps the compiled path honest.\n#\n# Needs mcpp >= 0.0.94: older versions never compiled a dependency's active\n# feature `sources` under `mcpp test`, so this member would fail to link.\n[package]\nname = \"spdlog-compiled-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nspdlog = { version = \"1.17.0\", features = [\"compiled\"] }\n", + "files": [ + { + "path": "tests/examples/spdlog-compiled/tests/compiled_test.cpp", + "text": "// Behavioral test: consume compat.spdlog in COMPILED mode. Asserts both halves\n// of what the `compiled` feature promises:\n//\n// 1. the interface define reached this TU (SPDLOG_COMPILED_LIB defined, and\n// spdlog's headers therefore did NOT flip on SPDLOG_HEADER_ONLY) — a\n// static check, so a regression is a compile error, not a silent fallback\n// to the header-only path that would still pass the runtime assertions;\n// 2. spdlog's src/*.cpp really were compiled and linked — the calls below\n// resolve to out-of-line symbols (default_logger_raw, log_msg's ctor,\n// logger::log_it_, and bundled fmt's vformat), which is exactly what fails\n// to link when the engine skips a feature's sources.\n#include \n#include \n#include \n#include \n\n#ifndef SPDLOG_COMPILED_LIB\n#error \"compiled feature did not propagate SPDLOG_COMPILED_LIB to the consumer\"\n#endif\n#ifdef SPDLOG_HEADER_ONLY\n#error \"SPDLOG_HEADER_ONLY is on in compiled mode — headers took the inline path\"\n#endif\n\nint main() {\n std::ostringstream captured;\n auto sink = std::make_shared(captured);\n spdlog::logger logger(\"test\", sink);\n logger.set_pattern(\"%v\"); // message only, no timestamp/level decoration\n\n logger.info(\"hello {}={}\", \"answer\", 42);\n logger.warn(\"{:#x}\", 255);\n logger.flush();\n\n // Exercises the global registry too — default_logger_raw() is one of the\n // out-of-line symbols that only exists once src/spdlog.cpp is compiled.\n spdlog::set_level(spdlog::level::info);\n\n const std::string out = captured.str();\n const bool ok = out.find(\"hello answer=42\") != std::string::npos\n && out.find(\"0xff\") != std::string::npos;\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "spdlog", + "text": "# spdlog test project: consumes compat.spdlog in its DEFAULT header-only mode\n# (bundled fmt, no external dependency) and asserts behavior under `mcpp test`.\n# Part of the mcpp-index self-referential workspace: the workspace-root\n# `[indices]` redirect points at this repo, so the dependency resolves to the\n# checked-in recipe\n# (pkgs/c/compat.spdlog.lua). Compiled mode is covered by the sibling member\n# tests/examples/spdlog-compiled/.\n[package]\nname = \"spdlog-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nspdlog = \"1.17.0\"\n", + "files": [ + { + "path": "tests/examples/spdlog/tests/log_test.cpp", + "text": "// Behavioral test: consume compat.spdlog in its DEFAULT header-only mode. Logs\n// through a custom logger wired to an in-memory ostream sink, then asserts the\n// formatted output — this exercises the logger, the pattern formatter, and the\n// bundled {fmt} formatting all via the header inlines. Returns non-zero on any\n// mismatch.\n#include \n#include \n#include \n#include \n\nint main() {\n std::ostringstream captured;\n auto sink = std::make_shared(captured);\n spdlog::logger logger(\"test\", sink);\n logger.set_pattern(\"%v\"); // message only, no timestamp/level decoration\n\n logger.info(\"hello {}={}\", \"answer\", 42);\n logger.warn(\"{:#x}\", 255);\n logger.flush();\n\n const std::string out = captured.str();\n const bool ok = out.find(\"hello answer=42\") != std::string::npos\n && out.find(\"0xff\") != std::string::npos;\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "spirv-headers", + "text": "[package]\nname = \"spirv-headers-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nspirv-headers = \"1.4.357.0\"\n", + "files": [ + { + "path": "tests/examples/spirv-headers/tests/decode.cpp", + "text": "// Behavioral test -- verify compat.spirv-headers delivers the Khronos include\n// layout and definitions a SPIR-V READER needs.\n//\n// The criterion is deliberately not \"the header compiles\". A header-only\n// package that resolves and exposes the wrong include root compiles nothing\n// and fails at the consumer, so this test spells the include the way a\n// consumer spells it and then decodes a module built by hand, which is the\n// only thing that can tell a real header tree from an empty one.\n#include \n\nimport std;\n\nint main() {\n // A minimal, well-formed SPIR-V module header: magic, version, generator,\n // bound, schema. Written as words rather than read from a file so the test\n // depends on nothing but the package under test.\n const std::uint32_t module_words[] = {\n spv::MagicNumber,\n spv::Version,\n 0u, // generator\n 1u, // bound\n 0u, // schema\n };\n\n if (module_words[0] != 0x07230203u) {\n std::println(\"spv::MagicNumber is {:#x}, expected 0x07230203\", module_words[0]);\n return 1;\n }\n\n // The enums a decoder switches on. Their numeric values are part of the\n // SPIR-V specification, so a header that renamed or renumbered them would\n // be a different specification rather than a newer package.\n if (static_cast(spv::OpCapability) != 17u) {\n std::println(\"spv::OpCapability is {}, expected 17\",\n static_cast(spv::OpCapability));\n return 2;\n }\n if (static_cast(spv::ExecutionModelGLCompute) != 5u) {\n std::println(\"spv::ExecutionModelGLCompute is {}, expected 5\",\n static_cast(spv::ExecutionModelGLCompute));\n return 3;\n }\n\n // The version word packs major and minor into the middle two bytes. A\n // consumer that gates on a SPIR-V version reads it exactly this way.\n const std::uint32_t major = (module_words[1] >> 16) & 0xffu;\n const std::uint32_t minor = (module_words[1] >> 8) & 0xffu;\n if (major != 1u) {\n std::println(\"spv::Version reports major {}, expected 1\", major);\n return 4;\n }\n\n std::println(\"compat.spirv-headers: SPIR-V {}.{} definitions present\", major, minor);\n return 0;\n}\n" + } + ] + }, + { + "project": "spirv-reflect", + "text": "# spirv-reflect test project: reflect a REAL compute shader (compiled with\n# glslc and embedded as a word array) and assert the descriptor bindings,\n# sets and push-constant block come back with the values the shader declared.\n[package]\nname = \"spirv-reflect-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nspirv-reflect = \"1.4.357.0\"\n", + "files": [ + { + "path": "tests/examples/spirv-reflect/tests/reflect.cpp", + "text": "// compat.spirv-reflect — reflect a REAL compute shader and assert the bindings\n// come back with the values the shader declared.\n//\n// The module below is the SPIR-V that glslc emits for this GLSL:\n//\n// #version 450\n// layout(set = 0, binding = 3, std430) buffer Data { float values[]; } data;\n// layout(set = 1, binding = 0) uniform Params { vec4 tint; } params;\n// layout(push_constant) uniform Push { mat4 mvp; } push;\n// layout(local_size_x = 64) in;\n// void main() { data.values[gl_GlobalInvocationID.x] *= params.tint.x + push.mvp[0][0]; }\n//\n// It is embedded as words rather than compiled at test time so the test needs\n// no shader compiler on the runner. Two sets, two different descriptor types, a\n// non-zero binding number and a push-constant block mean a stub that returned\n// zeroed structures could not pass.\n#include \nimport std;\n\nnamespace {\n// glslc -fshader-stage=compute, SPIR-V 1.0 / Vulkan 1.0\nconstexpr std::uint32_t kComputeSpv[] = {\n 0x07230203, 0x00010000, 0x000d000b, 0x0000002b, 0x00000000, 0x00020011,\n 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,\n 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0006000f, 0x00000005,\n 0x00000004, 0x6e69616d, 0x00000000, 0x00000010, 0x00060010, 0x00000004,\n 0x00000011, 0x00000040, 0x00000001, 0x00000001, 0x00030003, 0x00000002,\n 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70,\n 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004,\n 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572,\n 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00040005,\n 0x00000008, 0x61746144, 0x00000000, 0x00050006, 0x00000008, 0x00000000,\n 0x756c6176, 0x00007365, 0x00040005, 0x0000000a, 0x61746164, 0x00000000,\n 0x00080005, 0x00000010, 0x475f6c67, 0x61626f6c, 0x766e496c, 0x7461636f,\n 0x496e6f69, 0x00000044, 0x00040005, 0x00000016, 0x61726150, 0x0000736d,\n 0x00050006, 0x00000016, 0x00000000, 0x746e6974, 0x00000000, 0x00040005,\n 0x00000018, 0x61726170, 0x0000736d, 0x00040005, 0x0000001d, 0x68737550,\n 0x00000000, 0x00040006, 0x0000001d, 0x00000000, 0x0070766d, 0x00040005,\n 0x0000001f, 0x68737570, 0x00000000, 0x00040047, 0x00000007, 0x00000006,\n 0x00000004, 0x00050048, 0x00000008, 0x00000000, 0x00000023, 0x00000000,\n 0x00030047, 0x00000008, 0x00000003, 0x00040047, 0x0000000a, 0x00000022,\n 0x00000000, 0x00040047, 0x0000000a, 0x00000021, 0x00000003, 0x00040047,\n 0x00000010, 0x0000000b, 0x0000001c, 0x00050048, 0x00000016, 0x00000000,\n 0x00000023, 0x00000000, 0x00030047, 0x00000016, 0x00000002, 0x00040047,\n 0x00000018, 0x00000022, 0x00000001, 0x00040047, 0x00000018, 0x00000021,\n 0x00000000, 0x00040048, 0x0000001d, 0x00000000, 0x00000005, 0x00050048,\n 0x0000001d, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001d,\n 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x0000001d, 0x00000002,\n 0x00040047, 0x0000002a, 0x0000000b, 0x00000019, 0x00020013, 0x00000002,\n 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020,\n 0x0003001d, 0x00000007, 0x00000006, 0x0003001e, 0x00000008, 0x00000007,\n 0x00040020, 0x00000009, 0x00000002, 0x00000008, 0x0004003b, 0x00000009,\n 0x0000000a, 0x00000002, 0x00040015, 0x0000000b, 0x00000020, 0x00000001,\n 0x0004002b, 0x0000000b, 0x0000000c, 0x00000000, 0x00040015, 0x0000000d,\n 0x00000020, 0x00000000, 0x00040017, 0x0000000e, 0x0000000d, 0x00000003,\n 0x00040020, 0x0000000f, 0x00000001, 0x0000000e, 0x0004003b, 0x0000000f,\n 0x00000010, 0x00000001, 0x0004002b, 0x0000000d, 0x00000011, 0x00000000,\n 0x00040020, 0x00000012, 0x00000001, 0x0000000d, 0x00040017, 0x00000015,\n 0x00000006, 0x00000004, 0x0003001e, 0x00000016, 0x00000015, 0x00040020,\n 0x00000017, 0x00000002, 0x00000016, 0x0004003b, 0x00000017, 0x00000018,\n 0x00000002, 0x00040020, 0x00000019, 0x00000002, 0x00000006, 0x00040018,\n 0x0000001c, 0x00000015, 0x00000004, 0x0003001e, 0x0000001d, 0x0000001c,\n 0x00040020, 0x0000001e, 0x00000009, 0x0000001d, 0x0004003b, 0x0000001e,\n 0x0000001f, 0x00000009, 0x00040020, 0x00000020, 0x00000009, 0x00000006,\n 0x0004002b, 0x0000000d, 0x00000028, 0x00000040, 0x0004002b, 0x0000000d,\n 0x00000029, 0x00000001, 0x0006002c, 0x0000000e, 0x0000002a, 0x00000028,\n 0x00000029, 0x00000029, 0x00050036, 0x00000002, 0x00000004, 0x00000000,\n 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000012, 0x00000013,\n 0x00000010, 0x00000011, 0x0004003d, 0x0000000d, 0x00000014, 0x00000013,\n 0x00060041, 0x00000019, 0x0000001a, 0x00000018, 0x0000000c, 0x00000011,\n 0x0004003d, 0x00000006, 0x0000001b, 0x0000001a, 0x00070041, 0x00000020,\n 0x00000021, 0x0000001f, 0x0000000c, 0x0000000c, 0x00000011, 0x0004003d,\n 0x00000006, 0x00000022, 0x00000021, 0x00050081, 0x00000006, 0x00000023,\n 0x0000001b, 0x00000022, 0x00060041, 0x00000019, 0x00000024, 0x0000000a,\n 0x0000000c, 0x00000014, 0x0004003d, 0x00000006, 0x00000025, 0x00000024,\n 0x00050085, 0x00000006, 0x00000026, 0x00000025, 0x00000023, 0x00060041,\n 0x00000019, 0x00000027, 0x0000000a, 0x0000000c, 0x00000014, 0x0003003e,\n 0x00000027, 0x00000026, 0x000100fd, 0x00010038,};\n} // namespace\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n SpvReflectShaderModule mod{};\n const auto rc = spvReflectCreateShaderModule(sizeof(kComputeSpv), kComputeSpv, &mod);\n check(rc == SPV_REFLECT_RESULT_SUCCESS, \"module parsed\");\n if (rc != SPV_REFLECT_RESULT_SUCCESS) return 1;\n\n check(mod.shader_stage == SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT, \"stage is compute\");\n check(std::string_view{mod.entry_point_name} == \"main\", \"entry point is main\");\n\n // ---- descriptor bindings --------------------------------------------\n std::uint32_t count = 0;\n check(spvReflectEnumerateDescriptorBindings(&mod, &count, nullptr) == SPV_REFLECT_RESULT_SUCCESS,\n \"binding count query\");\n check(count == 2, \"two descriptor bindings\");\n\n std::vector bindings(count);\n check(spvReflectEnumerateDescriptorBindings(&mod, &count, bindings.data()) == SPV_REFLECT_RESULT_SUCCESS,\n \"binding enumeration\");\n\n bool saw_storage = false, saw_uniform = false;\n for (const auto* b : bindings) {\n if (b->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) {\n saw_storage = true;\n check(b->set == 0, \"storage buffer is in set 0\");\n check(b->binding == 3, \"storage buffer is at binding 3\");\n } else if (b->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) {\n saw_uniform = true;\n check(b->set == 1, \"uniform buffer is in set 1\");\n check(b->binding == 0, \"uniform buffer is at binding 0\");\n }\n }\n check(saw_storage, \"the std430 buffer was reflected as a storage buffer\");\n check(saw_uniform, \"the uniform block was reflected as a uniform buffer\");\n\n // ---- descriptor sets -------------------------------------------------\n std::uint32_t sets = 0;\n check(spvReflectEnumerateDescriptorSets(&mod, &sets, nullptr) == SPV_REFLECT_RESULT_SUCCESS,\n \"set count query\");\n check(sets == 2, \"two descriptor sets\");\n\n // ---- push constants --------------------------------------------------\n std::uint32_t blocks = 0;\n check(spvReflectEnumeratePushConstantBlocks(&mod, &blocks, nullptr) == SPV_REFLECT_RESULT_SUCCESS,\n \"push constant count query\");\n check(blocks == 1, \"one push constant block\");\n\n std::vector pcs(blocks);\n if (blocks == 1) {\n check(spvReflectEnumeratePushConstantBlocks(&mod, &blocks, pcs.data()) == SPV_REFLECT_RESULT_SUCCESS,\n \"push constant enumeration\");\n check(pcs[0]->size == 64, \"the mat4 push constant block is 64 bytes\");\n check(pcs[0]->offset == 0, \"push constant block starts at offset 0\");\n }\n\n spvReflectDestroyShaderModule(&mod);\n\n // ---- negative case ---------------------------------------------------\n // Garbage must be REJECTED. Without this, a stub that always returns\n // SUCCESS would pass everything above by accident.\n const std::uint32_t junk[] = {0xdeadbeefu, 0u, 0u, 0u, 0u, 0u, 0u, 0u};\n SpvReflectShaderModule bad{};\n check(spvReflectCreateShaderModule(sizeof(junk), junk, &bad) != SPV_REFLECT_RESULT_SUCCESS,\n \"a bad magic number is rejected\");\n\n if (ok) std::println(\"SPIRV-Reflect OK\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "sqlite3", + "text": "# SQLite test project: depends on compat.sqlite3 (built from source via this\n# repo's own index) and asserts behavior under `mcpp test`. Part of the\n# mcpp-index self-referential workspace.\n[package]\nname = \"sqlite3-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nsqlite3 = \"3.45.3\"\n", + "files": [ + { + "path": "tests/examples/sqlite3/tests/sqlite3_test.cpp", + "text": "// Behavioral test for compat.sqlite3 (SQLite 3.45.3, C API): assert the\n// resolved library version, then open an in-memory database, create a table,\n// insert a row, and read it back through both sqlite3_exec and a prepared\n// statement.\n#include \n#include \n#include \n\nint main() {\n // The index pins 3.45.3; prove the resolved package is that version.\n assert(std::strcmp(sqlite3_libversion(), \"3.45.3\") == 0);\n\n sqlite3* db = nullptr;\n assert(sqlite3_open(\":memory:\", &db) == SQLITE_OK);\n assert(db != nullptr);\n\n // DDL + DML through the convenience API.\n char* errmsg = nullptr;\n int rc = sqlite3_exec(db,\n \"CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT NOT NULL);\"\n \"INSERT INTO t(name) VALUES ('mcpp');\",\n nullptr, nullptr, &errmsg);\n assert(rc == SQLITE_OK);\n if (errmsg != nullptr) { sqlite3_free(errmsg); errmsg = nullptr; }\n\n // Read back through a prepared statement.\n sqlite3_stmt* stmt = nullptr;\n rc = sqlite3_prepare_v2(db, \"SELECT id, name FROM t;\", -1, &stmt, nullptr);\n assert(rc == SQLITE_OK);\n assert(stmt != nullptr);\n assert(sqlite3_step(stmt) == SQLITE_ROW);\n assert(sqlite3_column_int(stmt, 0) == 1);\n assert(std::strcmp(reinterpret_cast(sqlite3_column_text(stmt, 1)), \"mcpp\") == 0);\n assert(sqlite3_step(stmt) == SQLITE_DONE);\n sqlite3_finalize(stmt);\n\n assert(sqlite3_close(db) == SQLITE_OK);\n return 0;\n}\n" + } + ] + }, + { + "project": "sqlitecpp", + "text": "# SQLiteCpp test project: consumes compat.sqlitecpp from this repo's own index.\n# compat.sqlite3 arrives transitively — the wrapper depends on it because\n# upstream's own build gets SQLite from a git submodule the tarball omits.\n[package]\nname = \"sqlitecpp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nsqlitecpp = \"3.3.3\"\n", + "files": [ + { + "path": "tests/examples/sqlitecpp/tests/database.cpp", + "text": "// Behavioral test: a real database session through the RAII wrapper.\n//\n// The load-bearing question for this package is whether the dependency edge on\n// compat.sqlite3 actually replaces upstream's git submodule — and that is a LINK\n// question, so every assertion below is chosen to reach sqlite3 symbols: the\n// version string, prepared statements with bound parameters, a transaction that\n// rolls back, and the exception path (which carries an sqlite3 error code).\n#include \n\n#include \n#include \n\nint main() {\n // The wrapper reports the SQLite it was linked against, which is the\n // cheapest proof that the dependency edge resolved to a real library.\n const std::string version = SQLite::getLibVersion();\n assert(!version.empty() && version[0] == '3');\n\n SQLite::Database db(\":memory:\", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);\n\n db.exec(\"CREATE TABLE note (id INTEGER PRIMARY KEY, title TEXT NOT NULL, size INTEGER)\");\n assert(db.tableExists(\"note\"));\n\n // --- bound parameters through a prepared statement ---\n {\n SQLite::Statement insert(db, \"INSERT INTO note (title, size) VALUES (?, ?)\");\n for (const auto& [title, size] : {std::pair{\"alpha\", 10},\n {\"beta\", 20},\n {\"gamma\", 30}}) {\n insert.bind(1, title);\n insert.bind(2, size);\n assert(insert.exec() == 1);\n insert.reset();\n }\n }\n assert(db.execAndGet(\"SELECT COUNT(*) FROM note\").getInt() == 3);\n assert(db.getLastInsertRowid() == 3);\n\n // --- iteration, typed columns, and a named parameter ---\n {\n SQLite::Statement query(db, \"SELECT id, title, size FROM note WHERE size >= :floor ORDER BY id\");\n query.bind(\":floor\", 20);\n int rows = 0;\n int total = 0;\n while (query.executeStep()) {\n const int id = query.getColumn(0);\n const std::string title = query.getColumn(1).getString();\n total += static_cast(query.getColumn(2).getInt());\n assert(id >= 2 && !title.empty());\n ++rows;\n }\n assert(rows == 2 && total == 50);\n }\n\n // --- a transaction that is NOT committed must roll back on scope exit ---\n {\n SQLite::Transaction abandoned(db);\n db.exec(\"DELETE FROM note\");\n assert(db.execAndGet(\"SELECT COUNT(*) FROM note\").getInt() == 0);\n }\n assert(db.execAndGet(\"SELECT COUNT(*) FROM note\").getInt() == 3);\n\n // --- and one that is ---\n {\n SQLite::Transaction kept(db);\n db.exec(\"UPDATE note SET size = size * 2\");\n kept.commit();\n }\n assert(db.execAndGet(\"SELECT SUM(size) FROM note\").getInt() == 120);\n\n // --- the error path carries an sqlite3 error code, not just a message ---\n bool threw = false;\n try {\n db.exec(\"INSERT INTO note (title) VALUES (NULL)\"); // NOT NULL violation\n } catch (const SQLite::Exception& e) {\n threw = true;\n assert(e.getErrorCode() != 0);\n assert(std::string(e.what()).find(\"NOT NULL\") != std::string::npos);\n }\n assert(threw);\n\n return 0;\n}\n" + } + ] + }, + { + "project": "sycl-runtime", + "text": "# compat.sycl-runtime is a Linux-only adapter, and for the same reason\n# compat.cuda-driver is: an mcpp binary on Linux runs under mcpp's own loader,\n# so a soname that is not on its runtime search path is not found at all. macOS\n# and Windows have neither that loader arrangement nor this payload, so the\n# dependency is conditioned rather than the member being excluded -- the member\n# still builds on all three, which is what keeps the test source from rotting.\n[package]\nname = \"sycl-runtime-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nsycl-runtime = \"2026.09.10\"\n", + "files": [ + { + "path": "tests/examples/sycl-runtime/tests/farm.cpp", + "text": "// What compat.sycl-runtime is asserted to do.\n//\n// A machine with no GPU is a legitimate configuration and is what every runner\n// in this repository is, so the test cannot require a device. It asserts the\n// properties that hold on both kinds of machine.\n//\n// 1. The package resolves, builds and links. The failure this package exists\n// to prevent is a RUNTIME one -- `libsycl.so.9 not found on the search\n// path this artifact will actually use` -- so linking alone is not the\n// whole assertion.\n//\n// 2. THE FARM IS SUFFICIENT FOR ITS OWN MEMBERS. For every versioned library\n// in the farm, every SONAME in its DT_NEEDED is either in the farm too or\n// is one of the three the artifact itself has already loaded.\n//\n// 3. `libsycl.so.9` loads and carries the entry point every SYCL program\n// reaches the runtime through. Resolving a file is not the same as it\n// being the runtime.\n//\n// WHY (2) IS A DT_NEEDED WALK AND NOT `dlopen` OF EACH MEMBER, which is what\n// this test did first: dlopen measures the PROCESS, and the process has more\n// on its search path than this package put there. Measured -- with an OpenCL\n// dependency briefly declared, `compat:opencl-runtime`'s farm supplied\n// `libnvidia-ml.so.1`, so removing NVML from THIS farm changed nothing and the\n// test passed on a farm that was missing it. A package's test has to be able\n// to fail on that package alone.\n//\n// Before this walk existed, the assertion was three hand-written names --\n// `libsycl.so.9`, `libur_loader.so.0`, `libumf.so.1` -- against a farm of\n// twenty-five members, and the two members that could not load were not\n// among the three (mcpp#596). The population is enumerated now; only the\n// exceptions are named, and each one carries its reason.\n//\n// WHETHER A MEMBER'S CLOSURE IS SATISFIED IS A PACKAGING PROPERTY, NOT A\n// DEVICE PROPERTY. This machine has no Level Zero device and both Level Zero\n// adapters are complete; the two that were broken reported `cannot open shared\n// object file`, which is a statement about the farm.\n//\n// THE TWO LEGITIMATE ABSENCES:\n//\n// * A dangling farm entry. The farm links the host's NVIDIA driver through\n// `xim:libcuda-host-link`, whose symlinks are deliberately dangling on a\n// machine with no driver so that installing one later self-heals every\n// consumer. The entry is present, so the package did its part; the target\n// is the machine's answer.\n//\n// * A SONAME this package gets from a DECLARED DEPENDENCY rather than from\n// its own farm. Today that is `libOpenCL.so.1`: `compat:sycl-runtime`\n// depends on `compat:opencl`, whose shared library is deployed beside the\n// consumer's executable and is found there through `$ORIGIN`. The name is\n// listed here rather than resolved through the process, because resolving\n// through the process is what let another farm answer for\n// `libnvidia-ml.so.1` and hide the gap this test exists to catch. A short\n// list that a reader can check is the price of a criterion that cannot be\n// masked.\n//\n// Which devices exist is deliberately NOT asserted: that is the machine's\n// answer, not this package's.\n#include \n\n#ifndef __linux__\nint main() {\n std::printf(\"not applicable on this platform\\n\");\n return 0;\n}\n#else\n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\n\n// Loaded on the executable's own behalf before any farm member is asked for,\n// and resolved by SONAME without a search. The recipe says the same thing from\n// the other side, as the reason it does NOT farm them: a second C library in\n// one address space is the one failure worse than a missing library.\nbool provided_by_the_artifact(const std::string& soname) {\n return soname == \"libc.so.6\" || soname == \"libm.so.6\"\n || soname.rfind(\"ld-linux\", 0) == 0;\n}\n\n// Served by a declared dependency of this package, not by its farm.\n//\n// `compat:opencl` builds the Khronos ICD loader with the canonical soname and\n// mcpp deploys it beside the consumer's executable, where `$ORIGIN` finds it.\n// Naming it here keeps the farm's self-sufficiency assertion exact: everything\n// NOT on this list must be in the farm, and no other directory on the search\n// path can answer for it.\nbool served_by_a_declared_dependency(const std::string& soname) {\n return soname == \"libOpenCL.so.1\";\n}\n\n// The farm's directory, read from a library that is in it.\n//\n// Not spelled as a path: this test does not know where the package was\n// installed, and the artifact's own DT_RPATH is the only thing that does.\n// `libsycl.so.9` resolves through it and the link map reports the file that\n// was actually opened, so the directory is discovered the way the loader\n// discovers it.\nstd::string farm_dir() {\n void* h = dlopen(\"libsycl.so.9\", RTLD_LAZY | RTLD_LOCAL);\n if (!h) {\n std::printf(\"FAIL libsycl.so.9: %s\\n\", dlerror());\n return {};\n }\n struct link_map* map = nullptr;\n if (dlinfo(h, RTLD_DI_LINKMAP, &map) != 0 || !map || !map->l_name) {\n std::printf(\"FAIL libsycl.so.9: loaded, but its path is not readable\\n\");\n return {};\n }\n std::string path = map->l_name;\n auto slash = path.rfind('/');\n return slash == std::string::npos ? std::string(\".\") : path.substr(0, slash);\n}\n\nbool versioned_soname(const std::string& name) {\n // `libfoo.so.N...`, which is what the farm links and what dlopen asks for.\n // The `-gdb.py` sidecars beside the payload's libraries match a looser test.\n auto so = name.find(\".so.\");\n if (so == std::string::npos) return false;\n if (name.size() < so + 5) return false;\n if (name.size() >= 3 && name.compare(name.size() - 3, 3, \".py\") == 0)\n return false;\n return name[so + 4] >= '0' && name[so + 4] <= '9';\n}\n\n// A link whose target does not exist. The sentinel's self-heal shape.\nbool dangling(const std::string& path) {\n struct stat st {};\n struct stat lst {};\n if (lstat(path.c_str(), &lst) != 0) return false;\n return stat(path.c_str(), &st) != 0;\n}\n\n// The DT_NEEDED list of an ELF64 file.\n//\n// Read here rather than shelled out to `objdump`/`readelf`: a test that needs\n// a binutils on the runner is a test that skips. Returns false when the file\n// is not an ELF64 this reader understands, which the caller reports rather\n// than treating as an empty list -- \"could not look\" and \"nothing there\" are\n// the two readings this repository has been burned by conflating.\nbool needed_of(const std::string& path, std::vector& out) {\n FILE* f = std::fopen(path.c_str(), \"rb\");\n if (!f) return false;\n std::vector buf;\n std::fseek(f, 0, SEEK_END);\n long size = std::ftell(f);\n std::fseek(f, 0, SEEK_SET);\n if (size <= 0) { std::fclose(f); return false; }\n buf.resize(static_cast(size));\n bool ok = std::fread(buf.data(), 1, buf.size(), f) == buf.size();\n std::fclose(f);\n if (!ok || buf.size() < sizeof(Elf64_Ehdr)) return false;\n\n auto const* eh = reinterpret_cast(buf.data());\n if (std::memcmp(eh->e_ident, ELFMAG, SELFMAG) != 0) return false;\n if (eh->e_ident[EI_CLASS] != ELFCLASS64) return false;\n if (eh->e_phoff == 0 || eh->e_phentsize != sizeof(Elf64_Phdr)) return false;\n\n const Elf64_Dyn* dyn = nullptr;\n std::size_t dynCount = 0;\n for (unsigned i = 0; i < eh->e_phnum; ++i) {\n auto off = eh->e_phoff + static_cast(i) * eh->e_phentsize;\n if (off + sizeof(Elf64_Phdr) > buf.size()) return false;\n auto const* ph = reinterpret_cast(buf.data() + off);\n if (ph->p_type != PT_DYNAMIC) continue;\n if (ph->p_offset + ph->p_filesz > buf.size()) return false;\n dyn = reinterpret_cast(buf.data() + ph->p_offset);\n dynCount = ph->p_filesz / sizeof(Elf64_Dyn);\n }\n if (!dyn) return true; // statically linked: no DT_NEEDED, and that is a\n // complete answer rather than a failure to read\n\n // DT_STRTAB is a virtual address; map it back through the program headers\n // that actually cover it.\n std::uint64_t strtabVaddr = 0;\n for (std::size_t i = 0; i < dynCount && dyn[i].d_tag != DT_NULL; ++i)\n if (dyn[i].d_tag == DT_STRTAB) strtabVaddr = dyn[i].d_un.d_ptr;\n if (!strtabVaddr) return false;\n\n std::size_t strtabOff = 0;\n bool mapped = false;\n for (unsigned i = 0; i < eh->e_phnum; ++i) {\n auto off = eh->e_phoff + static_cast(i) * eh->e_phentsize;\n auto const* ph = reinterpret_cast(buf.data() + off);\n if (ph->p_type != PT_LOAD) continue;\n if (strtabVaddr < ph->p_vaddr || strtabVaddr >= ph->p_vaddr + ph->p_filesz)\n continue;\n strtabOff = ph->p_offset + (strtabVaddr - ph->p_vaddr);\n mapped = true;\n break;\n }\n if (!mapped || strtabOff >= buf.size()) return false;\n\n for (std::size_t i = 0; i < dynCount && dyn[i].d_tag != DT_NULL; ++i) {\n if (dyn[i].d_tag != DT_NEEDED) continue;\n auto at = strtabOff + dyn[i].d_un.d_val;\n if (at >= buf.size()) return false;\n out.emplace_back(reinterpret_cast(buf.data() + at));\n }\n return true;\n}\n\n} // namespace\n\nint main() {\n const std::string dir = farm_dir();\n if (dir.empty()) return 1;\n std::printf(\"farm: %s\\n\", dir.c_str());\n\n DIR* d = opendir(dir.c_str());\n if (!d) {\n std::printf(\"FAIL %s: cannot be read\\n\", dir.c_str());\n return 1;\n }\n std::vector members;\n while (dirent* e = readdir(d)) {\n std::string name = e->d_name;\n if (versioned_soname(name)) members.push_back(name);\n }\n closedir(d);\n\n auto in_farm = [&](const std::string& soname) {\n for (auto const& m : members) if (m == soname) return true;\n return false;\n };\n\n // The denominator, printed before any verdict. A farm that failed to build\n // enumerates nothing and every per-member assertion then passes.\n std::printf(\"members: %zu\\n\", members.size());\n if (members.empty()) {\n std::printf(\"FAIL: the farm is empty\\n\");\n return 1;\n }\n\n int bad = 0;\n std::size_t walked = 0;\n for (auto const& name : members) {\n const std::string path = dir + \"/\" + name;\n if (dangling(path)) {\n // A host library this machine does not have. The entry is present,\n // which is this package's half of the contract.\n std::printf(\"skip %s (dangling host link)\\n\", name.c_str());\n continue;\n }\n std::vector needed;\n if (!needed_of(path, needed)) {\n std::printf(\"FAIL %s: DT_NEEDED could not be read\\n\", name.c_str());\n ++bad;\n continue;\n }\n ++walked;\n for (auto const& soname : needed) {\n if (provided_by_the_artifact(soname)) continue;\n if (in_farm(soname)) {\n if (dangling(dir + \"/\" + soname))\n std::printf(\"note %s needs %s, which is a dangling host \"\n \"link on this machine\\n\",\n name.c_str(), soname.c_str());\n continue;\n }\n if (served_by_a_declared_dependency(soname)) {\n std::printf(\"note %s needs %s, which a declared dependency of \"\n \"this package provides\\n\",\n name.c_str(), soname.c_str());\n continue;\n }\n std::printf(\"FAIL %s needs %s, which the farm does not carry\\n\",\n name.c_str(), soname.c_str());\n ++bad;\n }\n }\n // The second denominator. Every member could be a dangling link, and the\n // walk above would then have examined nothing while reporting no failure.\n std::printf(\"walked: %zu of %zu members\\n\", walked, members.size());\n if (walked == 0) {\n std::printf(\"FAIL: no member's closure was examined\\n\");\n ++bad;\n }\n\n // The entry point every SYCL program reaches the runtime through. Loading\n // the file is not the same as it being the runtime, and a farm that linked\n // a stale or wrong-class file would satisfy everything above.\n void* h = dlopen(\"libsycl.so.9\", RTLD_LAZY | RTLD_LOCAL);\n if (!h || !dlsym(h, \"__sycl_register_lib\")) {\n std::printf(\"FAIL libsycl.so.9: __sycl_register_lib is not there\\n\");\n ++bad;\n }\n\n std::printf(\"%s\\n\", bad ? \"FAILED\" : \"PASSED\");\n return bad ? 1 : 0;\n}\n#endif\n" + } + ] + }, + { + "project": "tinyhttps", + "text": "# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `default`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n[indices]\ndefault = { path = \"../../..\" }\n\n[package]\nname = \"tinyhttps-tests\"\nversion = \"0.1.0\"\n\n[dependencies]\ntinyhttps = \"0.3.4\"\n", + "files": [ + { + "path": "tests/examples/tinyhttps/tests/api.cpp", + "text": "import std;\nimport mcpplibs.tinyhttps;\n\nint main() {\n using namespace mcpplibs::tinyhttps;\n\n const auto request = HttpRequest::post(\"https://example.invalid/data\", \"{\\\"ok\\\":true}\");\n const auto proxy = parse_proxy_url(\"http://127.0.0.1:8088/path\");\n const auto chunk = parse_chunk_size_line(\"1a\");\n const auto invalid_chunk = parse_chunk_size_line(\"1x\");\n\n // 0.3.0: a status line must be one. The rejected string is the exact input\n // from mcpplibs/tinyhttps#15, where leftover body bytes were mined for\n // digits and reported as an ordinary 999.\n const auto status = parse_status_line(\"HTTP/1.1 200 OK\");\n const auto not_a_status = parse_status_line(\"BBBB 999 XHTTP/1.1 200 OK\");\n\n // 0.3.0: adding fields to this aggregate left four-initialiser\n // brace-initialisation working, which is the whole compatibility claim.\n HttpResponse response{204, \"No Content\", {}, {}};\n\n // 0.3.4: a stop token on all three entry points. One stopped before the\n // call ends it before anything is sent, so this needs no network and no\n // listener; the result says why without anyone matching text.\n std::stop_source stopped;\n stopped.request_stop();\n HttpClient client;\n HttpRequest get;\n get.url = \"https://example.invalid/\";\n const auto cancelled = client.send(get, stopped.get_token());\n const auto cancelledDownload = client.download_to_file(\n \"https://example.invalid/file\", \"tinyhttps-unused\", nullptr, nullptr,\n stopped.get_token());\n const bool ok = request.method == Method::POST\n && request.url == \"https://example.invalid/data\"\n && request.body == \"{\\\"ok\\\":true}\"\n && request.headers.at(\"Content-Type\") == \"application/json\"\n && proxy.host == \"127.0.0.1\"\n && proxy.port == 8088\n && chunk == 26\n && !invalid_chunk.has_value()\n && status.has_value()\n && status->code == 200\n && status->text == \"OK\"\n && !not_a_status.has_value()\n && response.bodyComplete // 0.3.0, defaults to true\n && response.bodyError.empty()\n && response.ok()\n && !response.cancelled // 0.3.4, defaults to false\n && cancelled.cancelled\n && cancelled.statusCode == 0\n && !cancelled.ok()\n && cancelledDownload.cancelled\n && !cancelledDownload.ok();\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "tray", + "text": "[package]\nname = \"tray-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\ntray = \"0.0.0-8dd1358\"\n", + "files": [ + { + "path": "tests/examples/tray/tests/header.cpp", + "text": "// Smoke test — verify compat.tray header compiles (header-only lib)\n#define TRAY_IMPLEMENTATION\n#include \"tray.h\"\nimport std;\n\nint main() {\n std::println(\"compat.tray smoke test: ok\");\n return 0;\n}\n" + } + ] + }, + { + "project": "usockets", + "text": "# uSockets test project. compat.libuv arrives transitively as the event loop.\n[package]\nname = \"usockets-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nusockets = \"0.8.8\"\n\n[build]\n# libusockets.h changes the layout of us_loop_t under LIBUS_USE_LIBUV and gates\n# its SSL declarations on LIBUS_NO_SSL, so a consumer must be compiled with the\n# SAME pair the library was — an index descriptor's cflags reach only the\n# package's own TUs. Getting this wrong does not fail to build; it corrupts.\ndefines = [\"LIBUS_USE_LIBUV\", \"LIBUS_NO_SSL\"]\n", + "files": [ + { + "path": "tests/examples/usockets/tests/loop.cpp", + "text": "// Behavioral test: a real event loop iteration, driven by uSockets' own timer.\n//\n// The struct-layout hazard this package warns about is invisible to a build:\n// `us_loop_t` has a different shape under LIBUS_USE_LIBUV, and a consumer that\n// disagrees corrupts memory rather than failing to link. So the test allocates\n// loop-attached extension memory, writes to it from a timer callback, and reads\n// it back afterwards — if the layouts disagreed, that write lands somewhere else.\n#include \n\n#include \n#include \n\nnamespace {\n\nstruct LoopExt {\n int ticks;\n int magic;\n};\n\nstruct TimerExt {\n us_loop_t* loop;\n int budget;\n};\n\nconstexpr int kMagic = 0x5AFE;\n\nvoid on_wakeup(us_loop_t*) {}\nvoid on_pre(us_loop_t*) {}\nvoid on_post(us_loop_t*) {}\n\nvoid on_timer(us_timer_t* timer) {\n auto* text = static_cast(us_timer_ext(timer));\n auto* lext = static_cast(us_loop_ext(text->loop));\n\n assert(lext->magic == kMagic && \"loop extension memory moved — layout mismatch\");\n ++lext->ticks;\n\n if (--text->budget <= 0) {\n us_timer_close(timer);\n }\n}\n\n} // namespace\n\nint main() {\n us_loop_t* loop = us_create_loop(nullptr, on_wakeup, on_pre, on_post,\n static_cast(sizeof(LoopExt)));\n assert(loop != nullptr);\n\n auto* lext = static_cast(us_loop_ext(loop));\n std::memset(lext, 0, sizeof(LoopExt));\n lext->magic = kMagic;\n\n us_timer_t* timer = us_create_timer(loop, 0, static_cast(sizeof(TimerExt)));\n assert(timer != nullptr);\n auto* text = static_cast(us_timer_ext(timer));\n text->loop = loop;\n text->budget = 3;\n\n // 1 ms, repeating: the loop must actually run and re-arm.\n us_timer_set(timer, on_timer, 1, 1);\n\n us_loop_run(loop); // returns when nothing is left referencing the loop\n\n assert(lext->ticks == 3);\n assert(lext->magic == kMagic);\n\n us_loop_free(loop);\n return 0;\n}\n" + } + ] + }, + { + "project": "uwebsockets", + "text": "# uWebSockets test project. compat.usockets and compat.libuv arrive transitively.\n[package]\nname = \"uwebsockets-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nuwebsockets = \"20.79.0\"\n\n[build]\n# uWS is header-only: the consumer's TU is where it is actually compiled, so the\n# consumer is where these have to be declared. LIBUS_* must match how uSockets\n# was built (us_loop_t layout, SSL declarations); UWS_NO_ZLIB matches the\n# package's zlib-free build.\ndefines = [\"LIBUS_USE_LIBUV\", \"LIBUS_NO_SSL\", \"UWS_NO_ZLIB\"]\n", + "files": [ + { + "path": "tests/examples/uwebsockets/tests/listen.cpp", + "text": "// Behavioral test: instantiate the server templates and bind a real port.\n//\n// The risk in a header-only package layered on a C library is not \"does the\n// header exist\" — it is whether the templates instantiate over the C types as\n// the library was actually built, which only the CONSUMER's compile can answer.\n// Registering routes instantiates HttpContext/HttpResponse/HttpRequest over\n// us_socket_t, and `listen` walks all the way down to a bound socket.\n#include \n\n#include \n#include \n\nint main() {\n bool bound = false;\n int chosen_port = 0;\n\n uWS::App app;\n\n // Two route shapes and a websocket behavior: each instantiates a different\n // corner of the template stack.\n app.get(\"/ping\", [](auto* res, auto* /*req*/) {\n res->end(\"pong\");\n });\n app.post(\"/echo/:id\", [](auto* res, auto* req) {\n res->end(std::string(req->getParameter(0)));\n });\n app.ws(\"/ws\", {\n .open = [](auto* /*ws*/) {},\n .message = [](auto* ws, std::string_view msg, uWS::OpCode op) { ws->send(msg, op); },\n });\n\n // Port 0 asks the OS for an ephemeral port, so the test cannot collide with\n // anything else on the runner.\n app.listen(0, [&](us_listen_socket_t* token) {\n bound = (token != nullptr);\n if (token) {\n chosen_port = us_socket_local_port(0, reinterpret_cast(token));\n us_listen_socket_close(0, token);\n }\n });\n\n // Returns once the listen socket is closed and nothing else holds the loop.\n app.run();\n\n assert(bound && \"uWS::App failed to bind an ephemeral port\");\n assert(chosen_port > 0 && \"the OS assigned no port\");\n return 0;\n}\n" + } + ] + }, + { + "project": "vulkan-hpp-module", + "text": "# Vulkan-Hpp module test project: consumes Khronos' own C++23 module units\n# (`import vulkan;` / `import vulkan_video;`) from the `khronos.vulkan-hpp`\n# package and asserts behavior under `mcpp test`.\n#\n# Overrides the workspace-root redirect: root declares `compat`, this member\n# needs `khronos`. A member-level [indices] REPLACES the inherited table rather\n# than merging with it, which is what keeps this to ONE project index repo —\n# two repos pointing at the same tree make every lookup ambiguous.\n#\n# The consequence is deliberate and worth stating: `compat.vulkan`, which\n# `khronos.vulkan-hpp` depends on for the loader, then resolves from the\n# PUBLISHED index rather than from this checkout. That is the same arrangement\n# tests/examples/asio-ssl leans on, and it is the right one here — the package\n# under test is the module package, and its dependency is a package this index\n# has been shipping for some time.\n[indices]\nkhronos = { path = \"../../..\" }\n\n[package]\nname = \"vulkan-hpp-module-tests\"\nversion = \"0.1.0\"\n\n[dependencies.khronos]\nvulkan-hpp = \"1.4.357.1\"\n\n# No [build] section, and no per-platform gating: unlike tests/examples/vulkan,\n# which had to publish its own HAVE_VULKAN_LOADER switch, nothing here is\n# conditional. The package carries all three platforms, and every runtime call\n# the tests make is one the loader answers by itself, before any ICD is\n# involved — so the same source runs on a driverless runner on all of them.\n", + "files": [ + { + "path": "tests/examples/vulkan-hpp-module/tests/module.cpp", + "text": "// Behavioral test — consume Vulkan-Hpp through the C++23 named module\n// `vulkan`, and assert the things a module build can get wrong quietly.\n//\n// There is not a single `#include` here, on purpose. `import vulkan;` has to\n// deliver the whole surface by itself: a package that resolved, downloaded and\n// linked but never compiled its module unit fails at the FIRST line of this\n// file rather than passing with an empty build.\n//\n// The three groups below are chosen so that each of Vulkan-Hpp's four header\n// layers is answered by something only that layer provides, and none of them\n// needs a GPU, a driver, or an ICD manifest:\n//\n// * vulkan.hpp — enums, structs, the `sType` defaults\n// * vulkan_format_traits.hpp — constexpr format arithmetic\n// * vulkan_extension_inspection.hpp— the extension registry tables\n// * vulkan_raii.hpp — the RAII wrappers, asserted by TYPE\n//\n// `vk::to_string` is asserted below for a reason worth stating: it lives in\n// `vulkan_to_string.hpp`, which is NOT in the list of headers `vulkan.cppm`\n// names — reading that list as exhaustive gets this wrong. `vulkan.hpp`\n// includes it itself, guarded only by `VULKAN_HPP_NO_TO_STRING`, which nothing\n// defines here, so it rides into the module purview and is exported. The check\n// exists so that stops being a matter of reading and stays true.\n//\n// The rule the module DOES impose: `import vulkan;` and\n// `#include ` are two roads and a TU takes exactly one of\n// them — the header would arrive a second time, textually, as a different set\n// of entities from the ones the module already owns.\n//\n// Macros do not travel through a named module either, which is why the\n// extension names below are written as string literals rather than as\n// `VK_KHR_SURFACE_EXTENSION_NAME`. Nor do the C API's own names: upstream's\n// module unit exports the `vk::` surface plus the `PFN_vk*` function-pointer\n// typedefs, and nothing else from the C headers — so `VkInstance` and\n// `VkApplicationInfo` cannot be named here, only reached through\n// `vk::Instance` and friends.\nimport std;\nimport vulkan;\n\nint main() {\n // ── vulkan.hpp: enumerators and structs ──────────────────────────────\n //\n // Enumerator VALUES are the classic silent loss in a module wrapper — a\n // wrapper that exports only an enum's type name still compiles, and the\n // enumerators just are not there. These are the Vulkan specification's own\n // numbers (VK_FORMAT_R8G8B8A8_UNORM is 37), so a module that answers them\n // is carrying the real enum.\n static_assert(static_cast(vk::Format::eR8G8B8A8Unorm) == 37u);\n static_assert(static_cast(vk::ImageLayout::ePresentSrcKHR) == 1000001002u);\n\n // A struct with its generated `sType` default: this is Vulkan-Hpp's whole\n // reason to exist over the C headers, and it is default-member-initializer\n // state that has to survive the module build intact.\n static_assert(vk::ApplicationInfo{}.sType == vk::StructureType::eApplicationInfo);\n static_assert(std::is_standard_layout_v);\n\n // ── vulkan_to_string.hpp, which arrives through vulkan.hpp ──────────\n //\n // Not in `vulkan.cppm`'s include list, exported all the same. Both\n // overloads: the enum one and the bitmask one, which is a different\n // generated file section.\n if (vk::to_string(vk::Format::eR8G8B8A8Unorm) != \"R8G8B8A8Unorm\") {\n std::println(\"vk::to_string(Format) gave {}\", vk::to_string(vk::Format::eR8G8B8A8Unorm));\n return 5;\n }\n if (vk::to_string(vk::ImageUsageFlagBits::eColorAttachment) != \"ColorAttachment\") {\n std::println(\"vk::to_string(ImageUsageFlagBits) gave {}\",\n vk::to_string(vk::ImageUsageFlagBits::eColorAttachment));\n return 6;\n }\n\n // ── vulkan_format_traits.hpp: constexpr, evaluated at compile time ───\n static_assert(vk::blockSize(vk::Format::eR8G8B8A8Unorm) == 4);\n static_assert(vk::componentCount(vk::Format::eR8G8B8A8Unorm) == 4);\n static_assert(vk::blockSize(vk::Format::eBc1RgbUnormBlock) == 8);\n\n // ── vulkan_raii.hpp: asserted by type, not by construction ───────────\n //\n // Constructing a `vk::raii::Context` would dlopen the loader at RUNTIME,\n // which is a different claim than \"the RAII layer is in the module\" and is\n // not answerable on every runner (macOS links the loader statically, so\n // there is no library to open). The static members below are compile-time\n // facts of the RAII classes, so they prove the layer is present without\n // asking anything of the machine.\n static_assert(vk::raii::Instance::objectType == vk::ObjectType::eInstance);\n static_assert(vk::raii::Device::objectType == vk::ObjectType::eDevice);\n static_assert(std::is_same_v);\n\n // ── The loader, reached through the module's function layer ──────────\n //\n // Vulkan-Hpp defaults to the STATIC dispatcher (VULKAN_HPP_DISPATCH_LOADER_\n // DYNAMIC is 0 unless VK_NO_PROTOTYPES is defined), so these calls become\n // direct references to `vkEnumerateInstanceVersion` and friends. That is\n // the link-time proof that `khronos.vulkan-hpp`'s dependency on\n // `compat.vulkan` reaches a consumer: without it, this file does not link.\n //\n // Both answers come from the loader itself, before any ICD is consulted,\n // so they hold on a runner with no GPU. (The WSI surface extensions do NOT\n // — the loader advertises those only when some driver supports them, which\n // is why nothing here asserts on `VK_KHR_surface` being *reported*.)\n const std::uint32_t apiVersion = vk::enumerateInstanceVersion();\n if (vk::apiVersionMajor(apiVersion) < 1) {\n std::println(\"implausible loader api version: {}\", apiVersion);\n return 1;\n }\n\n const std::vector extensions = vk::enumerateInstanceExtensionProperties();\n\n // VK_EXT_debug_utils is implemented BY the loader, so it is present with or\n // without a driver. Reading it back through `vk::ExtensionProperties`\n // exercises the ArrayWrapper1D member that makes the C struct's\n // `char[256]` a real C++ string type.\n const bool haveDebugUtils = std::ranges::any_of(extensions, [](const vk::ExtensionProperties& e) {\n return std::string_view(e.extensionName) == \"VK_EXT_debug_utils\";\n });\n if (!haveDebugUtils) {\n std::println(\"VK_EXT_debug_utils missing from {} loader extension(s)\", extensions.size());\n for (const auto& e : extensions) std::println(\" {}\", std::string_view(e.extensionName));\n return 2;\n }\n\n // ── vulkan_extension_inspection.hpp: the registry tables ─────────────\n //\n // This is static knowledge compiled from the Vulkan XML registry, not a\n // question about this machine — `VK_KHR_swapchain` is a device extension\n // whether or not anything here implements it. A module missing this header\n // would fail to compile above; a module carrying an EMPTY table would\n // compile and answer false, which is what these check.\n if (!vk::isInstanceExtension(\"VK_KHR_surface\") || vk::isDeviceExtension(\"VK_KHR_surface\")) {\n std::println(\"extension inspection misclassified VK_KHR_surface\");\n return 3;\n }\n if (!vk::isDeviceExtension(\"VK_KHR_swapchain\")) {\n std::println(\"extension inspection does not know VK_KHR_swapchain is a device extension\");\n return 4;\n }\n\n std::println(\"khronos.vulkan-hpp: ok (import vulkan; loader api {}.{}.{}, {} instance extension(s))\",\n vk::apiVersionMajor(apiVersion), vk::apiVersionMinor(apiVersion),\n vk::apiVersionPatch(apiVersion), extensions.size());\n return 0;\n}\n" + }, + { + "path": "tests/examples/vulkan-hpp-module/tests/video.cpp", + "text": "// Behavioral test — the package's SECOND module unit, `vulkan_video`.\n//\n// It gets its own test file for a reason that is not tidiness: upstream's\n// `vulkan_video.cppm` says `import vulkan;`, so this module can only build\n// after the other one, from a source list that names them in no particular\n// order. A build system that compiled the two units independently would fail\n// here and nowhere else, and a package that shipped only the first unit would\n// fail at the `import` line below while `module.cpp` still passed.\n//\n// Everything asserted is compile-time video-codec-standard data (the H.264\n// numbers are ITU-T H.264's own), so this test needs no GPU and no video\n// hardware — only the module.\nimport std;\nimport vulkan_video;\n\nint main() {\n // Constants: `constexpr inline` variables at namespace scope inside the\n // module purview. They are the shape most likely to be lost.\n static_assert(vk::video::H264MaxNumListRef == 32);\n static_assert(vk::video::H264ScalingList8X8NumElements == 64);\n static_assert(vk::video::H264MaxChromaPlanes == 2);\n\n // Enumerators, wrapping the STD_VIDEO_* values from the vk_video/ C\n // headers — which is also proof that those headers, reached through\n // `compat.vulkan-headers`, were on the include path when the module unit\n // was compiled: the enums live behind `#if defined(VULKAN_VIDEO_CODEC_\n // H264STD_H_)`, so a build that missed them would compile an EMPTY\n // namespace rather than fail.\n static_assert(static_cast(vk::video::H264ProfileIdc::eBaseline) == 66u);\n static_assert(static_cast(vk::video::H264ProfileIdc::eHigh) == 100u);\n static_assert(static_cast(vk::video::H264ChromaFormatIdc::e420) == 1u);\n\n // A struct from the same header set, to show the type layer is there too.\n const vk::video::H264SpsFlags flags{};\n if (flags.direct_8x8_inference_flag != 0u) {\n std::println(\"vk::video::H264SpsFlags is not value-initialized\");\n return 1;\n }\n\n std::println(\"khronos.vulkan-hpp: ok (import vulkan_video; H.264 profile idc baseline={}, high={})\",\n static_cast(vk::video::H264ProfileIdc::eBaseline),\n static_cast(vk::video::H264ProfileIdc::eHigh));\n return 0;\n}\n" + } + ] + }, + { + "project": "vulkan-memory-allocator", + "text": "# VMA test project: drive the VIRTUAL allocator, which is VMA's sub-allocation\n# algorithm with the Vulkan calls removed. That makes it the one part of VMA\n# that can be asserted for real on a CI machine with no GPU and no driver --\n# and it still proves the implementation TU is compiled and linked.\n[package]\nname = \"vulkan-memory-allocator-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nvulkan-memory-allocator = \"3.4.0\"\n", + "files": [ + { + "path": "tests/examples/vulkan-memory-allocator/tests/virtual_block.cpp", + "text": "// compat.vulkan-memory-allocator — drive VMA's VIRTUAL allocator.\n//\n// Everything else in VMA needs a VkDevice, which a CI runner with no GPU and no\n// driver cannot create. The virtual allocator is the same sub-allocation\n// algorithm with the Vulkan calls stripped out: it hands out offsets inside a\n// notional block. That makes it the one part of VMA whose BEHAVIOUR can be\n// asserted here -- and since it is compiled from the same VMA_IMPLEMENTATION\n// TU as the rest, it still proves the package's implementation is built and\n// linked (these symbols do not exist in the header-only view).\n#include \nimport std;\n\nint main() {\n bool ok = true;\n auto check = [&](bool cond, std::string_view what) {\n if (!cond) {\n std::println(\"FAIL: {}\", what);\n ok = false;\n }\n };\n\n constexpr VkDeviceSize BLOCK = 1u << 20; // 1 MiB\n\n VmaVirtualBlockCreateInfo bci{};\n bci.size = BLOCK;\n\n VmaVirtualBlock block = VK_NULL_HANDLE;\n check(vmaCreateVirtualBlock(&bci, &block) == VK_SUCCESS, \"virtual block created\");\n check(block != VK_NULL_HANDLE, \"block handle is non-null\");\n if (block == VK_NULL_HANDLE) return 1;\n\n check(vmaIsVirtualBlockEmpty(block), \"a fresh block is empty\");\n\n // Allocate a set of differently-sized, differently-aligned chunks.\n struct Chunk { VmaVirtualAllocation h; VkDeviceSize off, size; };\n std::vector chunks;\n for (int i = 0; i < 16; ++i) {\n VmaVirtualAllocationCreateInfo aci{};\n aci.size = static_cast(1024u << (i % 5));\n aci.alignment = 256;\n\n VmaVirtualAllocation h{};\n VkDeviceSize off = 0;\n if (vmaVirtualAllocate(block, &aci, &h, &off) != VK_SUCCESS) {\n check(false, \"vmaVirtualAllocate succeeded\");\n break;\n }\n check(off % 256 == 0, \"returned offset honours the requested alignment\");\n check(off + aci.size <= BLOCK, \"allocation stays inside the block\");\n chunks.push_back({h, off, aci.size});\n }\n check(chunks.size() == 16, \"all 16 allocations succeeded\");\n check(!vmaIsVirtualBlockEmpty(block), \"block is not empty while allocations live\");\n\n // The whole point of an allocator: no two live allocations may overlap.\n std::ranges::sort(chunks, {}, &Chunk::off);\n for (std::size_t i = 1; i < chunks.size(); ++i) {\n check(chunks[i - 1].off + chunks[i - 1].size <= chunks[i].off,\n \"live allocations do not overlap\");\n }\n\n // Statistics must account for what we asked for.\n VkDeviceSize requested = 0;\n for (const auto& c : chunks) requested += c.size;\n\n VmaStatistics stats{};\n vmaGetVirtualBlockStatistics(block, &stats);\n check(stats.allocationCount == chunks.size(), \"stats report every allocation\");\n check(stats.allocationBytes == requested, \"stats report the requested bytes\");\n\n // Freeing must return the space: after freeing everything the block is\n // empty again and a single block-sized allocation fits.\n for (const auto& c : chunks) vmaVirtualFree(block, c.h);\n check(vmaIsVirtualBlockEmpty(block), \"block is empty again after freeing all\");\n\n VmaVirtualAllocationCreateInfo whole{};\n whole.size = BLOCK;\n VmaVirtualAllocation big{};\n VkDeviceSize big_off = 0;\n check(vmaVirtualAllocate(block, &whole, &big, &big_off) == VK_SUCCESS,\n \"the entire block is allocatable once freed\");\n check(big_off == 0, \"the block-sized allocation starts at offset 0\");\n vmaVirtualFree(block, big);\n\n // And an over-sized request must FAIL rather than silently succeed.\n VmaVirtualAllocationCreateInfo toobig{};\n toobig.size = BLOCK * 2;\n VmaVirtualAllocation none{};\n VkDeviceSize none_off = 0;\n check(vmaVirtualAllocate(block, &toobig, &none, &none_off) != VK_SUCCESS,\n \"an over-sized request is rejected\");\n\n vmaDestroyVirtualBlock(block);\n\n if (ok) std::println(\"VulkanMemoryAllocator OK\");\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "vulkan", + "text": "[package]\nname = \"vulkan-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nvulkan = \"1.4.357.3\"\n\n# All three platforms now carry compat.vulkan — linux/macOS build the loader\n# from source, windows links the import library. HAVE_VULKAN_LOADER stays as the\n# switch tests/loader.cpp keys off; it is unconditional again.\n[build]\ncxxflags = [\"-DHAVE_VULKAN_LOADER=1\"]\n", + "files": [ + { + "path": "tests/examples/vulkan/tests/loader.cpp", + "text": "// Behavioral test — verify compat.vulkan builds a working loader and that a\n// consumer can link its trampolines.\n//\n// Everything asserted here is answered by the LOADER itself, before any ICD is\n// involved, so it is meaningful on a CI runner with no GPU and no driver.\n// That constraint is sharper than it looks: the loader advertises the WSI\n// surface extensions only when some ICD supports them, so `VK_KHR_surface` is\n// legitimately ABSENT on a driverless machine and asserting on it would just be\n// testing the runner's hardware.\n//\n// HAVE_VULKAN_LOADER is set by THIS project's own [build] cxxflags. A consumer\n// that keys its source off a dependency's presence has to declare that itself.\n#if defined(HAVE_VULKAN_LOADER)\n#include \n#endif\nimport std;\n\n#if defined(HAVE_VULKAN_LOADER) && defined(_WIN32)\n// Declared rather than taken from : two functions are needed, and\n// the header's macros and min/max collide with `import std`. Both live in\n// kernel32, which every Windows program already links.\nextern \"C\" __declspec(dllimport) void* GetModuleHandleW(const wchar_t* name);\nextern \"C\" __declspec(dllimport) unsigned long GetModuleFileNameW(void* module, wchar_t* path,\n unsigned long size);\n#endif\n\n#if !defined(HAVE_VULKAN_LOADER)\nint main() {\n std::println(\"compat.vulkan: skipped (no windows build — static loader unsupported upstream)\");\n return 0;\n}\n#else\nint main() {\n // Only the loader can answer this — it reports the Vulkan version the\n // loader itself implements. A package that failed to compile its\n // trampolines fails at LINK here rather than passing quietly.\n std::uint32_t apiVersion = 0;\n if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) {\n std::println(\"vkEnumerateInstanceVersion failed\");\n return 1;\n }\n if (VK_VERSION_MAJOR(apiVersion) < 1) {\n std::println(\"implausible loader api version: {}\", apiVersion);\n return 2;\n }\n\n#if defined(_WIN32)\n // WHERE THE LOADER CAME FROM is the point of compat.vulkan 1.4.357.3 on\n // Windows: the package now ships vulkan-1.dll and mcpp places it beside the\n // executable, so a machine with no GPU driver can still start this program.\n // The loader has just answered, so the DLL is mapped; its directory must be\n // this executable's. That holds on a machine WITH a driver too (the\n // application directory precedes System32), which is what makes this a real\n // check: with an older compat.vulkan the loader came from System32 there,\n // and on a driverless machine the process never reached main.\n {\n std::vector dll(32768), exe(32768);\n void* module = GetModuleHandleW(L\"vulkan-1.dll\");\n if (module == nullptr) {\n std::println(\"vulkan-1.dll answered but is not mapped in this process\");\n return 7;\n }\n const auto dllLen = GetModuleFileNameW(module, dll.data(), 32768);\n const auto exeLen = GetModuleFileNameW(nullptr, exe.data(), 32768);\n auto dirOf = [](std::wstring_view p) {\n std::wstring d(p.substr(0, p.find_last_of(L\"\\\\/\")));\n for (auto& c : d) c = static_cast(std::towlower(c));\n return d;\n };\n auto narrow = [](std::wstring_view p) {\n std::string out;\n for (wchar_t c : p) out.push_back(c < 128 ? static_cast(c) : '?');\n return out;\n };\n const std::wstring_view dllPath(dll.data(), dllLen), exePath(exe.data(), exeLen);\n if (dirOf(dllPath) != dirOf(exePath)) {\n std::println(\"vulkan-1.dll was loaded from {}, not beside {}\",\n narrow(dllPath), narrow(exePath));\n return 8;\n }\n std::println(\"compat.vulkan: loader deployed beside the executable ({})\", narrow(dllPath));\n }\n#endif\n\n std::uint32_t extensionCount = 0;\n if (vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr) != VK_SUCCESS) {\n std::println(\"vkEnumerateInstanceExtensionProperties failed\");\n return 3;\n }\n std::vector extensions(extensionCount);\n if (extensionCount > 0 &&\n vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()) != VK_SUCCESS) {\n std::println(\"vkEnumerateInstanceExtensionProperties (fill) failed\");\n return 4;\n }\n\n // VK_EXT_debug_utils is implemented BY the loader, so it is present with or\n // without a driver — unlike the surface extensions. Its absence would mean\n // the loader's own extension table never made it into the lib.\n const bool haveDebugUtils = std::ranges::any_of(extensions, [](const VkExtensionProperties& e) {\n return std::string_view(e.extensionName) == VK_EXT_DEBUG_UTILS_EXTENSION_NAME;\n });\n if (!haveDebugUtils) {\n std::println(\"VK_EXT_debug_utils missing from {} loader extension(s)\", extensionCount);\n for (const auto& e : extensions) std::println(\" {}\", e.extensionName);\n return 5;\n }\n\n // Link-time proof that wsi.c is in the library. vkDestroySurfaceKHR is a\n // WSI trampoline; referencing it makes a build without that translation\n // unit fail to link, which is the check the extension list cannot give us\n // on a driverless machine.\n if (&vkDestroySurfaceKHR == nullptr) {\n std::println(\"vkDestroySurfaceKHR unexpectedly null\");\n return 6;\n }\n\n std::println(\"compat.vulkan: ok (loader api {}.{}.{}, {} loader extension(s), WSI trampolines linked)\",\n VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion),\n VK_VERSION_PATCH(apiVersion), extensionCount);\n return 0;\n}\n#endif\n" + } + ] + }, + { + "project": "wamr-features", + "text": "# WAMR feature test project: both optional guest-libc layers turned on.\n#\n# Split from tests/examples/wamr because the base member asserts the opposite —\n# that `env.putchar` does NOT resolve without the feature. The two members\n# together are the feature gate's positive and negative evidence.\n[package]\nname = \"wamr-features-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nwamr = { version = \"2.4.5\", features = [\"libc-builtin\", \"libc-wasi\"] }\n", + "files": [ + { + "path": "tests/examples/wamr-features/tests/libc_features.cpp", + "text": "// Feature test for compat.wamr: assert that the two optional guest-libc layers\n// are actually linked in when requested.\n//\n// The discriminator is a CALL, not instantiation. WAMR does not reject a\n// module whose import is unresolved — the loader logs \"failed to link import\n// function\" and carries on, so instantiation succeeds either way. The refusal\n// only appears when the guest calls the import, as an exception naming it\n// \"unlinked\". tests/examples/wamr asserts exactly that for env.putchar against\n// the base package; here the same call must go through.\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\n// (module (import \"env\" \"putchar\" (func (param i32) (result i32)))\n// (func (export \"emit\") (param i32) (result i32) local.get 0 call 0))\nconst unsigned char kNeedsLibcBuiltin[] = {\n 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n 0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f,\n 0x02, 0x0f, 0x01, 0x03, 'e', 'n', 'v',\n 0x07, 'p', 'u', 't', 'c', 'h', 'a', 'r', 0x00, 0x00,\n 0x03, 0x02, 0x01, 0x00,\n 0x07, 0x08, 0x01, 0x04, 'e', 'm', 'i', 't', 0x00, 0x01,\n 0x0a, 0x08, 0x01, 0x06, 0x00, 0x20, 0x00, 0x10, 0x00, 0x0b,\n};\n\n// (module (import \"wasi_snapshot_preview1\" \"proc_exit\" (func (param i32)))\n// (memory (export \"memory\") 1)\n// (func (export \"exit0\") (param i32) local.get 0 call 0))\n//\n// The memory is not optional decoration: WAMR refuses to load a module that\n// imports WASI apis without an exported memory (\"a module with WASI apis must\n// export memory by default\"), because the WASI wrappers address the guest's\n// linear memory to return their results.\nconst unsigned char kNeedsLibcWasi[] = {\n 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n 0x01, 0x05, 0x01, 0x60, 0x01, 0x7f, 0x00,\n 0x02, 0x24, 0x01,\n 0x16, 'w', 'a', 's', 'i', '_', 's', 'n', 'a', 'p', 's', 'h', 'o', 't',\n '_', 'p', 'r', 'e', 'v', 'i', 'e', 'w', '1',\n 0x09, 'p', 'r', 'o', 'c', '_', 'e', 'x', 'i', 't', 0x00, 0x00,\n 0x03, 0x02, 0x01, 0x00,\n 0x05, 0x03, 0x01, 0x00, 0x01,\n 0x07, 0x12, 0x02, 0x05, 'e', 'x', 'i', 't', '0', 0x00, 0x01,\n 0x06, 'm', 'e', 'm', 'o', 'r', 'y', 0x02, 0x00,\n 0x0a, 0x08, 0x01, 0x06, 0x00, 0x20, 0x00, 0x10, 0x00, 0x0b,\n};\n\nbool g_ok = true;\n\nvoid check(const char *what, bool cond)\n{\n std::printf(\"%-28s %s\\n\", what, cond ? \"ok\" : \"FAILED\");\n g_ok = g_ok && cond;\n}\n\n} // namespace\n\nint main()\n{\n RuntimeInitArgs init;\n std::memset(&init, 0, sizeof init);\n init.mem_alloc_type = Alloc_With_Allocator;\n init.mem_alloc_option.allocator.malloc_func = reinterpret_cast(std::malloc);\n init.mem_alloc_option.allocator.realloc_func = reinterpret_cast(std::realloc);\n init.mem_alloc_option.allocator.free_func = reinterpret_cast(std::free);\n\n if (!wasm_runtime_full_init(&init)) {\n std::puts(\"wasm_runtime_full_init failed\");\n return 1;\n }\n\n char err[192] = { 0 };\n\n // ── libc-builtin: env.putchar must link and run ────────────────────────\n {\n unsigned char image[sizeof kNeedsLibcBuiltin];\n std::memcpy(image, kNeedsLibcBuiltin, sizeof image);\n wasm_module_t mod = wasm_runtime_load(image, sizeof image, err, sizeof err);\n check(\"libc-builtin load\", mod != nullptr);\n if (mod) {\n wasm_module_inst_t inst =\n wasm_runtime_instantiate(mod, 8192, 8192, err, sizeof err);\n check(\"libc-builtin instantiate\", inst != nullptr);\n if (inst) {\n wasm_function_inst_t fn = wasm_runtime_lookup_function(inst, \"emit\");\n wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 8192);\n if (fn && env) {\n // putchar writes the byte and returns 1 (see\n // libc_builtin_wrapper.c putchar_wrapper) -- it is not the\n // C library's \"returns the character written\".\n uint32_t argv[1] = { 0x0a };\n bool called = wasm_runtime_call_wasm(env, fn, 1, argv);\n if (!called)\n std::printf(\" exception: %s\\n\",\n wasm_runtime_get_exception(inst));\n check(\"libc-builtin putchar call\", called && argv[0] == 1);\n }\n else {\n check(\"libc-builtin lookup/env\", false);\n }\n if (env)\n wasm_runtime_destroy_exec_env(env);\n wasm_runtime_deinstantiate(inst);\n }\n wasm_runtime_unload(mod);\n }\n }\n\n // ── libc-wasi: wasi_snapshot_preview1.proc_exit must link and run ──────\n // proc_exit does not end the process; the wrapper raises \"wasi proc exit\"\n // and records the code, which the embedder then reads. That round trip is\n // only possible with the feature's sources compiled in.\n {\n unsigned char image[sizeof kNeedsLibcWasi];\n std::memcpy(image, kNeedsLibcWasi, sizeof image);\n wasm_module_t mod = wasm_runtime_load(image, sizeof image, err, sizeof err);\n if (!mod)\n std::printf(\" load error: %s\\n\", err);\n check(\"libc-wasi load\", mod != nullptr);\n if (mod) {\n // Gives the module a wasi context; the wrapper dereferences it.\n wasm_runtime_set_wasi_args(mod, nullptr, 0, nullptr, 0, nullptr, 0,\n nullptr, 0);\n wasm_module_inst_t inst =\n wasm_runtime_instantiate(mod, 8192, 8192, err, sizeof err);\n check(\"libc-wasi instantiate\", inst != nullptr);\n if (inst) {\n wasm_function_inst_t fn = wasm_runtime_lookup_function(inst, \"exit0\");\n wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 8192);\n if (fn && env) {\n uint32_t argv[1] = { 3 };\n bool called = wasm_runtime_call_wasm(env, fn, 1, argv);\n const char *ex = wasm_runtime_get_exception(inst);\n std::printf(\" call=%d exception=%s exit_code=%u\\n\", called ? 1 : 0,\n ex ? ex : \"(none)\",\n wasm_runtime_get_wasi_exit_code(inst));\n // The one thing that must NOT happen is the unlinked-import\n // refusal; that is the signal the feature was not compiled in.\n bool unlinked = ex && std::strstr(ex, \"unlinked\") != nullptr;\n check(\"libc-wasi proc_exit linked\", !unlinked);\n check(\"libc-wasi exit code\", wasm_runtime_get_wasi_exit_code(inst) == 3);\n }\n else {\n check(\"libc-wasi lookup/env\", false);\n }\n if (env)\n wasm_runtime_destroy_exec_env(env);\n wasm_runtime_deinstantiate(inst);\n }\n wasm_runtime_unload(mod);\n }\n }\n\n wasm_runtime_destroy();\n return g_ok ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "wamr", + "text": "# WAMR test project: load a hand-assembled wasm module, instantiate it, call an\n# export, and have the guest call back into a host native function.\n#\n# The module bytes are written out by hand rather than produced by a wasm\n# toolchain, so the test has no build-time dependency beyond the package itself.\n#\n# compat.wamr carries a `linux` section only (see the descriptor's header), so\n# the dependency is gated and the test compiles to a no-op main() elsewhere —\n# the same shape tests/examples/libaio uses.\n[package]\nname = \"wamr-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nwamr = \"2.4.5\"\n", + "files": [ + { + "path": "tests/examples/wamr/tests/run_module.cpp", + "text": "// Behavioral test for compat.wamr: run a real wasm module through the\n// interpreter and let it call back into the host.\n//\n// Nothing here is mocked. A package that links but mis-configures the runtime\n// would pass a \"does it link\" test: without BUILD_TARGET_* the invoke-native\n// section compiles to nothing, and without the generated dispatcher there is\n// no invokeNative at all — either way the guest→host call below is what\n// notices.\n#ifdef __linux__\n\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\n// (module\n// (import \"env\" \"host_mul\" (func $host_mul (param i32 i32) (result i32)))\n// (func (export \"compute\") (param i32 i32) (result i32)\n// local.get 0 local.get 1 call $host_mul))\nconst unsigned char kCallsHost[] = {\n 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,\n 0x02, 0x10, 0x01, 0x03, 'e', 'n', 'v',\n 0x08, 'h', 'o', 's', 't', '_', 'm', 'u', 'l', 0x00, 0x00,\n 0x03, 0x02, 0x01, 0x00,\n 0x07, 0x0b, 0x01, 0x07, 'c', 'o', 'm', 'p', 'u', 't', 'e', 0x00, 0x01,\n 0x0a, 0x0a, 0x01, 0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0b,\n};\n\n// Same shape, but importing `env.putchar` — one of the wrappers the\n// `libc-builtin` feature adds. The base package must NOT resolve it; this is\n// the negative half of the feature gate, with the positive half in\n// tests/examples/wamr-features.\nconst unsigned char kNeedsLibcBuiltin[] = {\n 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n 0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f,\n 0x02, 0x0f, 0x01, 0x03, 'e', 'n', 'v',\n 0x07, 'p', 'u', 't', 'c', 'h', 'a', 'r', 0x00, 0x00,\n 0x03, 0x02, 0x01, 0x00,\n 0x07, 0x08, 0x01, 0x04, 'e', 'm', 'i', 't', 0x00, 0x01,\n 0x0a, 0x08, 0x01, 0x06, 0x00, 0x20, 0x00, 0x10, 0x00, 0x0b,\n};\n\nint host_mul(wasm_exec_env_t, int a, int b) { return a * b; }\n\nNativeSymbol g_natives[] = {\n { \"host_mul\", reinterpret_cast(host_mul), \"(ii)i\", nullptr },\n};\n\n// Calls `emit` in the given module and reports whether WAMR refused because\n// the import was never linked.\n//\n// An unresolved import is NOT an instantiation error in WAMR — the loader only\n// logs \"failed to link import function\" and defers, so asking whether the\n// module instantiates says nothing about the feature gate. The refusal only\n// materialises on the call, as an exception naming the unlinked function.\nbool refused_as_unlinked(const unsigned char *bytes, unsigned size)\n{\n char err[192] = { 0 };\n // wasm_runtime_load takes a mutable buffer: it may patch the module image\n // in place, so hand it a copy rather than the const literal.\n unsigned char *buf = static_cast(std::malloc(size));\n if (!buf)\n return false;\n std::memcpy(buf, bytes, size);\n\n bool unlinked = false;\n wasm_module_t mod = wasm_runtime_load(buf, size, err, sizeof err);\n if (!mod) {\n std::printf(\" load rejected: %s\\n\", err);\n }\n else {\n wasm_module_inst_t inst =\n wasm_runtime_instantiate(mod, 8192, 8192, err, sizeof err);\n if (!inst) {\n std::printf(\" instantiate rejected: %s\\n\", err);\n }\n else {\n wasm_function_inst_t fn = wasm_runtime_lookup_function(inst, \"emit\");\n wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 8192);\n if (fn && env) {\n uint32_t argv[1] = { 65 };\n if (!wasm_runtime_call_wasm(env, fn, 1, argv)) {\n const char *ex = wasm_runtime_get_exception(inst);\n std::printf(\" exception: %s\\n\", ex ? ex : \"(none)\");\n unlinked = ex && std::strstr(ex, \"unlinked\") != nullptr;\n }\n }\n if (env)\n wasm_runtime_destroy_exec_env(env);\n wasm_runtime_deinstantiate(inst);\n }\n wasm_runtime_unload(mod);\n }\n std::free(buf);\n return unlinked;\n}\n\n} // namespace\n\nint main()\n{\n RuntimeInitArgs init;\n std::memset(&init, 0, sizeof init);\n init.mem_alloc_type = Alloc_With_Allocator;\n init.mem_alloc_option.allocator.malloc_func = reinterpret_cast(std::malloc);\n init.mem_alloc_option.allocator.realloc_func = reinterpret_cast(std::realloc);\n init.mem_alloc_option.allocator.free_func = reinterpret_cast(std::free);\n init.native_module_name = \"env\";\n init.native_symbols = g_natives;\n init.n_native_symbols = 1;\n\n bool ok = wasm_runtime_full_init(&init);\n if (!ok) {\n std::puts(\"wasm_runtime_full_init failed\");\n return 1;\n }\n\n char err[192] = { 0 };\n unsigned char image[sizeof kCallsHost];\n std::memcpy(image, kCallsHost, sizeof image);\n\n wasm_module_t mod = wasm_runtime_load(image, sizeof image, err, sizeof err);\n ok = ok && mod != nullptr;\n if (!mod)\n std::printf(\"load failed: %s\\n\", err);\n\n wasm_module_inst_t inst = nullptr;\n if (mod) {\n inst = wasm_runtime_instantiate(mod, 8192, 8192, err, sizeof err);\n ok = ok && inst != nullptr;\n if (!inst)\n std::printf(\"instantiate failed: %s\\n\", err);\n }\n\n if (inst) {\n wasm_function_inst_t fn = wasm_runtime_lookup_function(inst, \"compute\");\n ok = ok && fn != nullptr;\n if (fn) {\n wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 8192);\n ok = ok && env != nullptr;\n if (env) {\n uint32_t argv[2] = { 6, 7 };\n bool called = wasm_runtime_call_wasm(env, fn, 2, argv);\n if (!called)\n std::printf(\"call failed: %s\\n\",\n wasm_runtime_get_exception(inst));\n // 6 * 7 computed by the HOST and returned through the guest.\n ok = ok && called && argv[0] == 42u;\n std::printf(\"compute(6,7) = %u (expected 42)\\n\", argv[0]);\n wasm_runtime_destroy_exec_env(env);\n }\n }\n wasm_runtime_deinstantiate(inst);\n }\n if (mod)\n wasm_runtime_unload(mod);\n\n // Feature gate, negative direction: libc-builtin is not in the base build,\n // so a module importing env.putchar must be refused.\n std::puts(\"expecting env.putchar to be unlinked (libc-builtin is off):\");\n bool libc_absent =\n refused_as_unlinked(kNeedsLibcBuiltin, sizeof kNeedsLibcBuiltin);\n ok = ok && libc_absent;\n std::printf(\"libc-builtin gated out: %s\\n\", libc_absent ? \"yes\" : \"NO\");\n\n wasm_runtime_destroy();\n return ok ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "wayland-cursor", + "text": "# wayland-cursor test member — the client-side pointer.\n#\n# Own `[indices]` for the reason the sibling wayland members document: the\n# workspace root maps `compat` to this checkout and lookup is BY NAMESPACE.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n# ONE package named, and libwayland-client arrives with it — `wayland-cursor`\n# declares it as a PATH dependency (they are workspace members of one fork), and\n# naming it here as a version dependency too is rejected outright. Same shape as\n# tests/examples/wayland-egl; the note there explains why the diagnostic is\n# right.\n[package]\nname = \"wayland-cursor-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.freedesktop]\nwayland-cursor = \"1.26.0\"\n", + "files": [ + { + "path": "tests/examples/wayland-cursor/tests/wayland-cursor.cpp", + "text": "// freedesktop.wayland-cursor — the client-side pointer.\n//\n// WHAT CANNOT BE ASSERTED HERE, AND WHY THE FIRST VERSION OF THIS FILE CRASHED\n//\n// `wl_cursor_theme_load(name, size, shm)` is not callable without a compositor,\n// and not merely inadvisable: line 410 of upstream's wayland-cursor.c is\n//\n// theme->pool = shm_pool_create(shm, size * size * 4);\n//\n// which reaches `wl_shm_create_pool(shm, ...)` and dereferences the proxy with\n// no null check. The first version of this test passed `nullptr` on the theory\n// that only the theme PARSER would run — it segfaulted (exit 139). A `wl_shm`\n// comes from a compositor's registry, so the buffer half of this library\n// belongs in a program that has one, exactly as opening /dev/input/event*\n// belongs outside tests/examples/libinput.\n//\n// WHAT IS ASSERTED IS STILL THE PART THAT CARRIES THE PACKAGING RISK: that the\n// hand-written module wrapper exports every name, that the two PUBLIC structs\n// are usable as values rather than opaque handles, and that libwayland-client\n// is really linked in. The compiled-in cursor search path being empty — the\n// other packaging decision — is checked in the fork's CI by grepping the\n// binary, which is the only place it is visible.\n\n#ifdef __linux__\n\n#include \n#include \n\n#include \n#include \n#include \n\nimport freedesktop.wayland.cursor;\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. The module carries the API ────────────────────────────────────\n // Taking the address of each: a name that vanished from the hand-written\n // module wrapper is a compile error here. That is the guarantee\n // mcpp/tools/genmod.py gives the client and server wrappers, which this\n // one is too small to justify.\n check(&wl_cursor_theme_load != nullptr, \"module exports wl_cursor_theme_load\");\n check(&wl_cursor_theme_destroy != nullptr, \"module exports wl_cursor_theme_destroy\");\n check(&wl_cursor_theme_get_cursor != nullptr, \"module exports wl_cursor_theme_get_cursor\");\n check(&wl_cursor_image_get_buffer != nullptr, \"module exports wl_cursor_image_get_buffer\");\n check(&wl_cursor_frame != nullptr, \"module exports wl_cursor_frame\");\n check(&wl_cursor_frame_and_duration != nullptr, \"module exports wl_cursor_frame_and_duration\");\n\n // ── 2. The public structs are VALUES, not opaque handles ─────────────\n // `wl_cursor` and `wl_cursor_image` have their fields in the public header\n // and a client reads them to place the hotspot every time the pointer\n // enters a surface. Exporting them as opaque would compile here and fail\n // at the caller's first `image->hotspot_x`, which is why the fields are\n // touched rather than just the types named.\n wl_cursor_image img{};\n img.width = 24; img.height = 24;\n img.hotspot_x = 4; img.hotspot_y = 4; img.delay = 50;\n check(img.width == 24 && img.hotspot_x == 4 && img.delay == 50,\n \"wl_cursor_image fields are reachable through the module\");\n\n wl_cursor cur{};\n wl_cursor_image *one = &img;\n cur.image_count = 1;\n cur.images = &one;\n cur.name = const_cast(\"left_ptr\");\n check(cur.image_count == 1 && cur.images[0]->hotspot_y == 4,\n \"wl_cursor fields are reachable, including the images array\");\n\n // ── 3. wl_cursor_frame works on a theme-less cursor ──────────────────\n // The animation helper is pure arithmetic over the images array — no shm,\n // no compositor — so it IS callable here, and it is the one piece of real\n // behaviour this test can exercise. A single-image cursor is not animated,\n // so every timestamp must select frame 0.\n check(wl_cursor_frame(&cur, 0) == 0, \"wl_cursor_frame(t=0) selects the only frame\");\n check(wl_cursor_frame(&cur, 100000) == 0, \"…and so does a far-future timestamp\");\n\n uint32_t duration = 12345; // wayland-cursor.h includes , not \n const int f = wl_cursor_frame_and_duration(&cur, 0, &duration);\n std::printf(\" frame_and_duration -> frame %d, duration %u\\n\", f, duration);\n check(f == 0, \"wl_cursor_frame_and_duration agrees on the frame\");\n\n // ── 4. It links the client for real ──────────────────────────────────\n // Unlike libwayland-egl, this library CALLS into libwayland-client — that\n // is exactly why §3's theme loader needs a live wl_shm. The two are in one\n // program by necessity rather than by convention.\n //\n // `wl_display_connect` AND NOT `wl_shm_create_pool`, which is what this\n // check said first and what the llvm leg rejected:\n //\n // ld.lld: error: undefined symbol: wl_proxy_get_version\n // >>> wayland-cursor.o:(wl_shm_create_pool(wl_shm*, int, int))\n //\n // `wl_shm_create_pool` is one of wayland-scanner's `static inline`\n // protocol wrappers (mcpp/generated/wayland-client-protocol.h:2202), so\n // taking its address FORCES this translation unit to emit a copy — and\n // that copy calls `wl_proxy_marshal_flags` and friends, which live in\n // libwayland-client. GNU ld resolves those through the transitive\n // DT_NEEDED of libwayland-cursor; lld deliberately does not, and lld is\n // right: a program that names a symbol should link the library that\n // defines it.\n //\n // `wl_display_connect` is an ordinary exported function, so referencing it\n // proves the same thing without emitting anything. Same form the sibling\n // wayland-egl test uses, which is why that one was green on both legs.\n check(&wl_display_connect != nullptr,\n \"libwayland-client is linked in (wl_display_connect)\");\n\n // ── 5. Report the environment the ecosystem is expected to fill ───────\n // Not asserted: nothing in the index or in xim ships cursor themes yet, so\n // an unset XCURSOR_PATH is the current correct state rather than a defect.\n // When a theme package exists this becomes an assertion, exactly as the\n // RMLVO check in tests/examples/libxkbcommon did once xkeyboard-config\n // landed.\n const char *xp = std::getenv(\"XCURSOR_PATH\");\n std::printf(\" XCURSOR_PATH = %s\\n\",\n xp ? xp : \"(unset — no cursor-theme package exists yet)\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "wayland-egl", + "text": "# wayland-egl test member — the client-side GPU path.\n#\n# Its own `[indices]`, for the reason the sibling wayland member documents: the\n# workspace root maps `compat` to this checkout and lookup is BY NAMESPACE, so\n# `freedesktop.*` would otherwise resolve against the published index instead of\n# the packages under test.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"wayland-egl-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# ONE package named, and libwayland-client arrives with it.\n#\n# `freedesktop.wayland-egl`'s own manifest declares `freedesktop.wayland` as a\n# PATH dependency — the two are workspace members of the same fork — and naming\n# it here as a version dependency as well is rejected outright:\n#\n# error: dependency 'freedesktop.wayland' is requested as both a version\n# dep (by 'wayland-egl-tests') and a path dep (by\n# 'freedesktop.wayland-egl@1.26.0'). Pick one.\n#\n# That is the right diagnostic: two spellings of one dependency could resolve\n# to two different trees. So the client comes in transitively, which is also\n# what a real consumer gets — `wl_egl_window_create` takes a `wl_surface *`\n# that only libwayland-client can produce, so the edge exists whether or not\n# the consumer names it.\n[target.'cfg(linux)'.dependencies.freedesktop]\nwayland-egl = \"1.26.0\"\n", + "files": [ + { + "path": "tests/examples/wayland-egl/tests/wayland-egl.cpp", + "text": "// freedesktop.wayland-egl — the step that used to be missing.\n//\n// WHAT THIS CAN AND CANNOT ASSERT, decided by what the library actually is.\n//\n// libwayland-egl is 118 lines and holds no connection: `wl_egl_window_create`\n// allocates a struct, stores the `wl_surface *` it was handed, and records a\n// size. It calls no client function and talks to no compositor — an EGL\n// implementation later reads and writes the struct through\n// `wayland-egl-backend.h`.\n//\n// So a CI runner with no compositor can exercise the whole library honestly.\n// What it cannot do is `eglCreateWindowSurface`, which needs a real\n// `wl_display` from a real compositor — asserting that here would only assert\n// that the runner has a desktop session.\n//\n// THE BACKEND HEADER IS INCLUDED ON PURPOSE. `wayland-egl-core.h` exposes four\n// functions and an opaque type, so through it alone the only observable is\n// \"create returned non-null\". `wayland-egl-backend.h` is the contract this\n// library has with an EGL implementation — Mesa includes it — and reading the\n// struct through it is what lets the test check that create and resize wrote\n// what they claim.\n//\n// The `wl_surface *` is a null pointer ON PURPOSE: upstream stores it without\n// dereferencing (wayland-egl.c:93), so this exercises the real path, and the\n// test can then assert the pointer round-tripped.\n\n#ifdef __linux__\n\n#include \n#include \n#include \n\n#include \n#include \n\nimport freedesktop.wayland.egl;\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. The module carries the API ────────────────────────────────────\n // Taking the address of each is what makes this a check rather than a\n // comment: a name that vanished from the module is a compile error here,\n // which is the guarantee genmod.py gives the client and server wrappers\n // and that this hand-written module needs by other means.\n check(&wl_egl_window_create != nullptr, \"module exports wl_egl_window_create\");\n check(&wl_egl_window_destroy != nullptr, \"module exports wl_egl_window_destroy\");\n check(&wl_egl_window_resize != nullptr, \"module exports wl_egl_window_resize\");\n check(&wl_egl_window_get_attached_size != nullptr, \"module exports wl_egl_window_get_attached_size\");\n\n // ── 2. The bridge itself ─────────────────────────────────────────────\n // This is the call that did not exist in this ecosystem before: the only\n // way to turn a wl_surface into something EGL can render into.\n wl_egl_window *w = wl_egl_window_create(nullptr, 640, 480);\n check(w != nullptr, \"wl_egl_window_create(surface, 640, 480)\");\n if (w == nullptr) {\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return 1;\n }\n\n // ── 3. It wrote what it was given ────────────────────────────────────\n // Read back through the backend header, which is how Mesa reads it.\n // `version` matters most of the three: an EGL implementation switches on\n // it, and it is the field upstream casts away constness to set.\n std::printf(\" version=%ld size=%d x %d\\n\",\n (long)w->version, w->width, w->height);\n check(w->version == WL_EGL_WINDOW_VERSION, \"…and stamped WL_EGL_WINDOW_VERSION\");\n check(w->width == 640 && w->height == 480, \"…and recorded 640 x 480\");\n check(w->surface == nullptr, \"…and round-tripped the wl_surface pointer\");\n\n // ── 4. attached size is 0 BEFORE any EGL implementation attaches ─────\n // Not an oversight and not the creation size: `attached_width/height` are\n // written by the EGL side when it attaches a buffer, and `create` calloc's\n // them to zero. The first version of this test asserted 640 x 480 here and\n // was wrong — the check now encodes the real contract, which is that a\n // window nothing has rendered into reports nothing attached.\n int aw = -1, ah = -1;\n wl_egl_window_get_attached_size(w, &aw, &ah);\n std::printf(\" attached size before any EGL attach: %d x %d\\n\", aw, ah);\n check(aw == 0 && ah == 0, \"get_attached_size reports 0 x 0 until EGL attaches\");\n\n // ── 5. Resize is what a client calls on every configure event ────────\n wl_egl_window_resize(w, 800, 600, 0, 0);\n check(w->width == 800 && w->height == 600, \"wl_egl_window_resize updated the geometry\");\n\n // A rejected resize, which is a real branch: upstream returns early on a\n // non-positive dimension rather than storing it, so the previous size must\n // survive. Same guard `create` uses.\n wl_egl_window_resize(w, 0, 600, 0, 0);\n check(w->width == 800 && w->height == 600, \"…and a width of 0 is refused, not stored\");\n\n wl_egl_window_destroy(w);\n check(true, \"wl_egl_window_destroy\");\n\n // ── 6. create rejects a degenerate size ──────────────────────────────\n check(wl_egl_window_create(nullptr, 0, 480) == nullptr,\n \"wl_egl_window_create refuses a width of 0\");\n\n // ── 7. The two halves agree on the linkage they share ────────────────\n // `wl_display_connect` comes from libwayland-client and is NOT in\n // libwayland-egl — the fork's CI asserts that separation on the library,\n // and this asserts the consumer side: both are reachable from one program,\n // which is the arrangement a real client is in.\n check(&wl_display_connect != nullptr,\n \"libwayland-client is in the same program (wl_display_connect)\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "wayland-protocols", + "text": "# wayland-protocols test project.\n#\n# THIS MEMBER DECLARES ITS OWN [indices]: an index is looked up BY NAMESPACE,\n# and the workspace root maps `compat`, so `freedesktop.*` would resolve\n# against the published index rather than the packages under test.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"wayland-protocols-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# ONLY staging is named, and stable arrives with it.\n#\n# staging depends on stable INSIDE the fork, as a path dependency — measured\n# necessity, not convention: staging references `xdg_toplevel_interface` and\n# `zwp_tablet_tool_v2_interface`, which stable defines. So naming both here is\n# an error rather than a redundancy:\n#\n# dependency freedesktop.wayland-protocols-stable is requested as both a\n# version dep (by wayland-protocols-tests) and a path dep (by\n# freedesktop.wayland-protocols-staging.49). Pick one.\n#\n# The rule that falls out: a project that wants staging names staging; one that\n# wants only stable names stable. Never both. This member takes the first form,\n# and the test then proves that protocols from BOTH tiers link into one\n# program — which is the property the three-package split exists to preserve.\n#\n# stable + unstable is the pair that CANNOT coexist (13 shared symbols). That\n# is upstream's semantics rather than a defect, and testing it would mean\n# asserting a link failure.\n[target.'cfg(linux)'.dependencies.freedesktop]\nwayland-protocols-staging = \"1.49.1\"\n", + "files": [ + { + "path": "tests/examples/wayland-protocols/tests/wayland-protocols.cpp", + "text": "// wayland-protocols, as a compositor would consume it.\n//\n// The packages ship the marshalling code wayland-scanner generates from\n// upstream's XML. Three things can be wrong and none is a missing symbol:\n//\n// 1. THE HEADERS ARE THE PUBLIC INTERFACE. A consumer writes\n// `#include `, so the generated directory\n// has to be EXPOSED rather than build-private. This file compiling is the\n// first assertion, and freedesktop.wayland had exactly this bug once —\n// masked locally by a header the SubOS happened to carry.\n//\n// 2. THE INTERFACE TABLES MUST LINK. A header-only package sails past\n// compilation; `xdg_wm_base_interface` is a `wl_interface` OBJECT in the\n// generated .c, so reading it proves the .c files were compiled.\n//\n// 3. TWO TIERS MUST COMPOSE. stable and staging are separate packages\n// precisely so they can be used together; that they do is what this\n// member is for. (stable + unstable is the pair that cannot — 13 shared\n// symbols — which is upstream's semantics rather than a defect.)\n//\n// Nothing here connects: no compositor and no display.\n\n#ifdef __linux__\n\n#include \n#include \n\n#include // stable\n#include \n#include // stable — the GBM bridge\n#include // staging\n\n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\nvoid report(const wl_interface &i, const char *want, const char *tier)\n{\n std::printf(\" %-8s %-28s version %d, %d method(s)\\n\",\n tier, i.name ? i.name : \"(null)\", i.version, i.method_count);\n check(i.name != nullptr && std::strcmp(i.name, want) == 0,\n (std::string(tier) + \" \" + want + \" is linked in\").c_str());\n check(i.method_count > 0, \"…and it describes its requests\");\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. Both tiers linked, in one program ─────────────────────────────\n report(xdg_wm_base_interface, \"xdg_wm_base\", \"stable\");\n report(zwp_linux_dmabuf_v1_interface, \"zwp_linux_dmabuf_v1\", \"stable\");\n report(wp_cursor_shape_manager_v1_interface,\n \"wp_cursor_shape_manager_v1\", \"staging\");\n\n // ── 2. The server side was generated too ─────────────────────────────\n // `xdg_wm_base_send_ping` is a `static inline` only the SERVER header\n // defines. Client and server headers are separate scanner outputs and a\n // packaging mistake tends to lose one of them.\n check(reinterpret_cast(&xdg_wm_base_send_ping) != nullptr,\n \"the server-side headers were generated too\");\n\n // ── 3. The library that marshals these is new enough ─────────────────\n // The generated code calls wl_proxy_marshal_flags, which arrived in\n // libwayland 1.19 — so the protocols and the client library are not from\n // different eras.\n check(reinterpret_cast(&wl_proxy_marshal_flags) != nullptr,\n \"libwayland provides wl_proxy_marshal_flags\");\n check(reinterpret_cast(&wl_display_create) != nullptr,\n \"libwayland-server is linked for the compositor side\");\n\n // ── 4. The GBM bridge protocol is the stable one ─────────────────────\n // linux-dmabuf is how a compositor hands a gbm_bo to a client, so it is\n // the protocol that ties this package to compat.libgbm. It lives in\n // stable/ upstream; the unstable spelling is one of the three this index\n // deliberately does not ship.\n check(zwp_linux_dmabuf_v1_interface.version >= 4,\n \"linux-dmabuf is the stable v4+ interface, not the unstable spelling\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n// ── 1.49.1: the enum headers upstream installs ───────────────────────────────\n//\n// `wayland-scanner enum-header` output, which this fork did not generate before\n// 1.49.1. wlroots 0.20 includes these from TEN of its PUBLIC headers, so a\n// consumer of wlroots writing the ordinary `#include ` needs them on\n// the include path — which is why they belong to THIS package rather than to\n// wlroots.\n//\n// The enum header carries only the protocol's enums: no interface symbols and\n// no libwayland dependency. Including it in a translation unit that links\n// nothing proves exactly that.\n#include \n#include \n\nstatic_assert(XDG_TOPLEVEL_STATE_MAXIMIZED == 1,\n \"xdg-shell-enum.h did not come from the scanner\");\nstatic_assert(ZWP_TABLET_TOOL_V2_TYPE_PEN == 0x140,\n \"tablet-v2-enum.h did not come from the scanner\");\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + } + ] + }, + { + "project": "wayland", + "text": "# wayland test member.\n#\n# THIS MEMBER DECLARES ITS OWN [indices], and has to. The workspace root maps\n# the `compat` namespace to this checkout, and an index is looked up BY\n# NAMESPACE — so `freedesktop.*` would resolve against the published index\n# rather than the packages under test. A member-level table REPLACES the root's\n# rather than merging, which is what keeps this to one repo per member; the\n# transitive `compat.libffi` these packages need therefore comes from the\n# published index, which is correct: it is published.\n[indices]\nfreedesktop = { path = \"../../..\" }\n\n[package]\nname = \"wayland-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n# Two packages, not one with an ldflags escape hatch. The client and the server\n# are distinct SONAMEs — Mesa's libEGL_mesa carries DT_NEEDED on both — so they\n# are two libraries with disjoint contents, and a compositor asks for the one it\n# needs by name.\n#\n# wayland-util is the third: wayland's public MACROS, which no module can\n# export, mapped to the entities they actually are.\n[target.'cfg(linux)'.dependencies.freedesktop]\nwayland = \"1.26.0\"\nwayland-server = \"1.26.0\"\nwayland-util = \"1.26.0\"\n", + "files": [ + { + "path": "tests/examples/wayland/tests/modules.cpp", + "text": "// The module wrappers, used the way a consumer would.\n//\n// The claim they make is narrow and testable: `import freedesktop.wayland.client;` gives\n// you the same entities `#include ` does, spelled the same\n// way. So this file includes NOTHING from wayland — no header — and still\n// calls the stock API. If an export were missing, this would not compile; if a\n// name had been renamed or wrapped, it would not compile either.\n//\n// tests/wayland.cpp is the header-based sibling. Both must pass: the module\n// layer is an addition, not a replacement.\n\n#ifdef __linux__\n\n#include \n#include \n#include \n\nimport freedesktop.wayland.client;\nimport freedesktop.wayland.server;\nimport freedesktop.wayland.util;\n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) ++failures;\n}\n\n// A node linked through wl_list, the shape every wayland consumer writes.\nstruct listener_node {\n int id;\n wl_list link;\n};\n\n} // namespace\n\nint main()\n{\n // ── 1. Client entities, with no #include anywhere ────────────────────\n {\n wl_display *d = wl_display_connect(\"mcpp-no-such-compositor\");\n check(d == nullptr, \"wl_display_connect through the module returns NULL\");\n\n // A protocol interface object: generated code, exported by the module.\n check(std::string(wl_registry_interface.name) == \"wl_registry\",\n \"wl_registry_interface arrives through freedesktop.wayland.client\");\n check(std::string(wl_compositor_interface.name) == \"wl_compositor\",\n \"so does wl_compositor_interface\");\n }\n\n // ── 2. Server entities ───────────────────────────────────────────────\n {\n wl_display *s = wl_display_create();\n check(s != nullptr, \"wl_display_create through freedesktop.wayland.server\");\n if (s != nullptr) {\n wl_event_loop *loop = wl_display_get_event_loop(s);\n check(loop != nullptr, \"wl_display_get_event_loop returns a loop\");\n wl_display_destroy(s);\n }\n }\n\n // ── 3. The macros, as entities ───────────────────────────────────────\n // These are the names a module cannot export as macros; freedesktop.wayland.util\n // carries them as a constant, a template and ranges. Exercised here in a\n // consumer rather than only in the package's own test.\n {\n wl_list head;\n head.prev = &head;\n head.next = &head;\n\n listener_node a{1, {}}, b{2, {}};\n for (listener_node *n : {&a, &b}) {\n wl_list *prev = head.prev;\n n->link.prev = prev; n->link.next = &head;\n prev->next = &n->link; head.prev = &n->link;\n }\n\n std::vector seen;\n for (listener_node *n : wl_list_each<&listener_node::link>(&head)) {\n seen.push_back(n->id);\n }\n check(seen.size() == 2 && seen[0] == 1 && seen[1] == 2,\n \"wl_list_each walks a consumer's own list type\");\n\n check(wl_container_of<&listener_node::link>(&b.link) == &b,\n \"wl_container_of recovers the containing object\");\n\n check(WL_MARSHAL_FLAG_DESTROY == 1u,\n \"WL_MARSHAL_FLAG_DESTROY keeps upstream's value\");\n }\n\n // ⭐ 这八个函数在 mcpp4 之前根本够不着(`static inline` 无法从模块导出),\n // 而**没有任何测试调用过它们**,所以谁都没发现。缺口是从外面被一个最小\n // wlroots 合成器问出来的。下面每一行都是\"再塌陷就编译不过\"的探针。\n {\n // wayland-util.h 的定点数 —— 协议里每个亚像素坐标的载体\n check(wl_fixed_from_int(3) == 768, \"wl_fixed_from_int\");\n check(wl_fixed_to_double(wl_fixed_from_double(1.5)) == 1.5, \"wl_fixed double 往返\");\n check(wl_fixed_from_double(-1.5) == -384, \"负数按上游 round() 舍入\");\n\n // wayland-server-core.h 的 signal —— 每次 wlroots 挂监听都要它\n wl_signal sig{};\n wl_signal_init(&sig);\n check(sig.listener_list.next == &sig.listener_list, \"wl_signal_init\");\n wl_listener l{};\n l.notify = [](wl_listener *, void *) {};\n wl_signal_add(&sig, &l);\n check(wl_signal_get(&sig, l.notify) == &l, \"wl_signal_add + wl_signal_get\");\n wl_list_remove(&l.link);\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main() { return 0; }\n\n#endif\n" + }, + { + "path": "tests/examples/wayland/tests/wayland.cpp", + "text": "// compat.wayland — behavioral test, runnable with no compositor.\n//\n// Two seams, and the second is the one a compositor author depends on.\n//\n// 1. THE CLIENT LIBRARY, which the package puts on the link line itself.\n// `wl_display_connect` on a machine with no compositor must return NULL\n// cleanly -- that exercises the real library (it reads WAYLAND_DISPLAY,\n// builds a socket path and fails to connect) without needing a server.\n//\n// 2. THE LIBRARIES THE PACKAGE DELIBERATELY DOES NOT LINK. libwayland-server\n// is harvested into the farm but kept off `ldflags`, so a consumer reaches\n// it by adding `-lwayland-server` to its own build — which this member's\n// mcpp.toml does. If the farm ever stops carrying the server library, the\n// documented escape hatch silently stops working; this file is what turns\n// that into a link error here instead.\n\n#ifdef __linux__\n\n#include \n#include \n\n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-58s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. Headers resolved from the flat include root ───────────────────\n check(WL_DISPLAY_ERROR_INVALID_OBJECT == 0,\n \"wayland-client.h provides the core protocol enums\");\n\n // ── 2. Both libraries are really linked ──────────────────────────────\n for (const char *sym : {\"wl_display_connect\", \"wl_display_disconnect\",\n \"wl_proxy_marshal\", \"wl_registry_interface\"}) {\n check(::dlsym(RTLD_DEFAULT, sym) != nullptr,\n (std::string(\"libwayland-client exports \") + sym).c_str());\n }\n // This one comes from the farm via the consumer's own -lwayland-server,\n // not from anything this package puts on the link line.\n for (const char *sym : {\"wl_display_create\", \"wl_display_destroy\"}) {\n check(::dlsym(RTLD_DEFAULT, sym) != nullptr,\n (std::string(\"libwayland-server exports \") + sym).c_str());\n }\n\n // ── 3. Connecting with no compositor fails cleanly ───────────────────\n // Deliberately points at a socket that cannot exist, so the result does\n // not depend on whether the machine running the test has a session.\n ::setenv(\"WAYLAND_DISPLAY\", \"mcpp-no-such-compositor\", 1);\n ::unsetenv(\"WAYLAND_SOCKET\");\n wl_display *dpy = wl_display_connect(nullptr);\n std::printf(\" wl_display_connect (no compositor) = %p\\n\", (void *)dpy);\n check(dpy == nullptr,\n \"wl_display_connect returns NULL rather than crashing\");\n if (dpy != nullptr) {\n wl_display_disconnect(dpy);\n }\n\n // ── 4. The server library can actually build a display ───────────────\n // No socket is bound, so this needs no privileges and no session; it is\n // the cheapest proof that libwayland-server is functional and not merely\n // present.\n wl_display *server = wl_display_create();\n std::printf(\" wl_display_create = %p\\n\", (void *)server);\n check(server != nullptr, \"wl_display_create succeeds (server library live)\");\n if (server != nullptr) {\n wl_display_destroy(server);\n }\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\n\nint main()\n{\n return 0;\n}\n\n#endif\n" + } + ] + }, + { + "project": "websocket-features", + "text": "# websocket-features test project: consumes compat.websocket with the `server`\n# and `zlib` features enabled and asserts them under `mcpp test`. Part of the\n# mcpp-index self-referential workspace — the workspace-root `[indices]`\n# redirect points at this repo, so the dependency resolves to the checked-in\n# recipe (pkgs/c/compat.websocket.lua).\n#\n# The test is entirely OFFLINE: it runs an ix::WebSocketServer (the `server`\n# feature) on loopback and drives the ix::WebSocket client against it, then\n# checks that per-message-deflate (the `zlib` feature) actually compresses on\n# the wire. The sibling member tests/examples/websocket covers the zero-dep\n# client with no features; this member exists to prove the features.\n[package]\nname = \"websocket-features-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nwebsocket = { version = \"12.0.1\", features = [\"server\", \"zlib\"] }\n", + "files": [ + { + "path": "tests/examples/websocket-features/tests/ws_features_test.cpp", + "text": "// ws_features_test.cpp — asserts compat.websocket's `server` and `zlib`\n// features, enabled through the member's dependency declaration.\n//\n// * `server`: an ix::WebSocketServer runs inside the test process on\n// loopback and echoes every message; the client's round-trips go through\n// the REAL server class (the sibling member tests/examples/websocket uses\n// a hand-written raw-socket echo server precisely so the zero-dep client\n// is tested against something independent — here the point is the opposite:\n// prove the compiled-in server works).\n// * `zlib`: the client enables per-message-deflate. A highly-compressible\n// payload must come back as a message whose str is the full payload but\n// whose wireSize is the COMPRESSED frame bytes (the transport delivers\n// str = decompressed content and wireSize = raw wire payload), which is\n// how a compression round-trip is observable without sniffing the socket.\n//\n// No network access is required; everything stays on 127.0.0.1.\n\n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n// Raw sockets only for find_free_port() (a fixed test port would collide on\n// shared CI runners; port 0 is the race-free way to ask the OS).\n#ifdef _WIN32\n#include \n#include \nusing SockType = SOCKET;\nconstexpr SockType kInvalidSocket = INVALID_SOCKET;\n#else\n#include \n#include \n#include \n#include \nusing SockType = int;\nconstexpr SockType kInvalidSocket = -1;\n#endif\n\nnamespace\n{\n\nint g_failures = 0;\n\nvoid check(bool ok, const std::string& what)\n{\n if (ok)\n {\n std::cout << \" ok: \" << what << \"\\n\";\n }\n else\n {\n std::cout << \" FAIL: \" << what << \"\\n\";\n ++g_failures;\n }\n}\n\nint find_free_port()\n{\n#ifdef _WIN32\n WSADATA wsa;\n WSAStartup(MAKEWORD(2, 2), &wsa);\n#endif\n SockType s = socket(AF_INET, SOCK_STREAM, 0);\n if (s == kInvalidSocket) return -1;\n sockaddr_in addr{};\n addr.sin_family = AF_INET;\n addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n addr.sin_port = 0;\n if (bind(s, reinterpret_cast(&addr), sizeof(addr)) != 0)\n {\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n return -1;\n }\n#ifdef _WIN32\n int len = sizeof(addr);\n#else\n socklen_t len = sizeof(addr);\n#endif\n if (getsockname(s, reinterpret_cast(&addr), &len) != 0)\n {\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n return -1;\n }\n int port = ntohs(addr.sin_port);\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n return port;\n}\n\n// ── Client-side message collector ──────────────────────────────────────────\n\nstruct RecvMsg\n{\n ix::WebSocketMessageType type;\n std::string str;\n bool binary;\n size_t wireSize;\n};\n\nclass Collector\n{\npublic:\n void add(const ix::WebSocketMessagePtr& msg)\n {\n std::lock_guard lk(m_);\n msgs_.push_back(RecvMsg{msg->type, msg->str, msg->binary, msg->wireSize});\n cv_.notify_all();\n }\n\n template \n bool wait_for(Pred pred, int timeout_ms)\n {\n std::unique_lock lk(m_);\n return cv_.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return pred(msgs_); });\n }\n\nprivate:\n std::mutex m_;\n std::condition_variable cv_;\n std::vector msgs_;\n};\n\n} // namespace\n\nint main()\n{\n std::cout << std::unitbuf;\n\n bool netOk = ix::initNetSystem();\n check(netOk, \"ix::initNetSystem()\");\n\n int port = find_free_port();\n check(port > 0, \"found a free loopback port\");\n if (port <= 0)\n {\n ix::uninitNetSystem();\n return 1;\n }\n\n // The `server` feature: a real ix::WebSocketServer.\n ix::WebSocketServer server(port, \"127.0.0.1\");\n server.setOnClientMessageCallback(\n [](std::shared_ptr /*state*/,\n ix::WebSocket& ws,\n const ix::WebSocketMessagePtr& msg)\n {\n if (msg->type == ix::WebSocketMessageType::Message)\n {\n if (msg->binary) ws.sendBinary(msg->str);\n else ws.sendText(msg->str);\n }\n });\n check(server.listenAndStart(), \"ix::WebSocketServer listenAndStart()\");\n\n Collector collector;\n ix::WebSocket ws;\n ws.setUrl(\"ws://127.0.0.1:\" + std::to_string(port) + \"/\");\n ws.enablePerMessageDeflate();\n ws.setOnMessageCallback([&](const ix::WebSocketMessagePtr& msg) { collector.add(msg); });\n ws.start();\n\n check(collector.wait_for(\n [](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Open) return true;\n return false;\n },\n 5000),\n \"client opened against ix::WebSocketServer\");\n\n // Text round-trip through the real server.\n ws.sendText(\"hello, real server!\");\n check(collector.wait_for(\n [](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Message &&\n m.str == \"hello, real server!\" && !m.binary)\n return true;\n return false;\n },\n 5000),\n \"text message echoed by ix::WebSocketServer\");\n\n // Binary round-trip through the real server.\n std::string bin(\"\\x00\\x01\\xfe\\xff\\x80\\x01binary\", 10);\n ws.sendBinary(bin);\n check(collector.wait_for(\n [&](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Message && m.str == bin && m.binary)\n return true;\n return false;\n },\n 5000),\n \"binary message echoed by ix::WebSocketServer\");\n\n // The `zlib` feature: per-message-deflate must actually compress on the\n // wire. 64 KiB of a repeated byte compresses to a few hundred bytes; the\n // client delivers the FULL decompressed str but reports wireSize = the raw\n // (compressed) frame payload, so the ratio is observable.\n const size_t kBig = 65536;\n ws.sendText(std::string(kBig, 'a'));\n size_t compressedWire = 0;\n bool gotBig = collector.wait_for(\n [&](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Message && m.str.size() == kBig &&\n !m.binary)\n {\n compressedWire = m.wireSize;\n return true;\n }\n return false;\n },\n 5000);\n check(gotBig, \"64 KiB compressible message round-tripped\");\n if (gotBig)\n {\n check(compressedWire < 1024,\n \"per-message-deflate compressed the wire (wireSize=\" + std::to_string(compressedWire) +\n \" << 65536)\");\n }\n\n ws.stop();\n server.stop();\n ix::uninitNetSystem();\n\n if (g_failures == 0)\n {\n std::cout << \"ALL WEBSOCKET-FEATURES ASSERTIONS PASSED\\n\";\n return 0;\n }\n std::cout << g_failures << \" ASSERTION(S) FAILED\\n\";\n return 1;\n}\n" + } + ] + }, + { + "project": "websocket-tls", + "text": "# websocket-tls test project: proves compat.websocket's opt-in `tls` feature\n# makes a real certificate-validated wss:// client/server usable. The fixture\n# is entirely offline (127.0.0.1 + test-only PEM files), so CI neither reaches\n# a public WSS endpoint nor weakens IXWebSocket's normal peer/hostname checks.\n#\n# This is separate from `websocket` (strict zero-dependency ws:// client) and\n# `websocket-features` (server + zlib): mcpp resolves one feature set per\n# consuming project, so a separate member preserves those assertions.\n[package]\nname = \"websocket-tls-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(linux)'.dependencies.compat]\nwebsocket = { version = \"12.0.1\", features = [\"tls\", \"server\"] }\n\n[target.'cfg(linux)'.build]\ncxxflags = [\"-DHAVE_WEBSOCKET_TLS=1\"]\n\n[target.'cfg(macos)'.dependencies.compat]\nwebsocket = { version = \"12.0.1\", features = [\"tls\", \"server\"] }\n\n[target.'cfg(macos)'.build]\ncxxflags = [\"-DHAVE_WEBSOCKET_TLS=1\"]\n\n[target.'cfg(windows)'.dependencies.compat]\nwebsocket = { version = \"12.0.1\", features = [\"tls\", \"server\"] }\n\n[target.'cfg(windows)'.build]\ncxxflags = [\"-DHAVE_WEBSOCKET_TLS=1\"]\n", + "files": [ + { + "path": "tests/examples/websocket-tls/tests/wss_test.cpp", + "text": "// wss_test.cpp — offline end-to-end test for compat.websocket's `tls` feature.\n//\n// An ix::WebSocketServer serves TLS on loopback using test-only PEM files;\n// ix::WebSocket connects with wss://localhost and trusts that certificate via\n// caFile. Hostname validation intentionally stays enabled, so this verifies\n// OpenSSL transport, trust-chain handling, the localhost SAN, WebSocket\n// upgrade, text/binary payloads, and orderly shutdown without public network.\n#ifdef HAVE_WEBSOCKET_TLS\n\n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#ifdef _WIN32\n#include \n#include \nusing Socket = SOCKET;\nconstexpr Socket kInvalidSocket = INVALID_SOCKET;\n#else\n#include \n#include \n#include \n#include \nusing Socket = int;\nconstexpr Socket kInvalidSocket = -1;\n#endif\n\nnamespace\n{\n\nint g_failures = 0;\n\nvoid check(bool ok, const std::string& what)\n{\n if (ok)\n {\n std::cout << \" ok: \" << what << \"\\n\";\n }\n else\n {\n std::cout << \" FAIL: \" << what << \"\\n\";\n ++g_failures;\n }\n}\n\nstruct RecvMsg\n{\n ix::WebSocketMessageType type;\n std::string str;\n bool binary;\n};\n\nclass Collector\n{\npublic:\n void add(const ix::WebSocketMessagePtr& msg)\n {\n std::lock_guard lock(mutex_);\n messages_.push_back({msg->type, msg->str, msg->binary});\n condition_.notify_all();\n }\n\n template \n bool wait_for(Predicate predicate, int timeout_ms)\n {\n std::unique_lock lock(mutex_);\n return condition_.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&] {\n return predicate(messages_);\n });\n }\n\nprivate:\n std::mutex mutex_;\n std::condition_variable condition_;\n std::vector messages_;\n};\n\nconstexpr const char* kCertificatePem = R\"pem(-----BEGIN CERTIFICATE-----\nMIIDJTCCAg2gAwIBAgIUFgJ40WV0h6eUam72FKECeBPFXqkwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDgyNDEzMTQ0NVoXDTM2MDgy\nMTEzMTQ0NVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAvdaQ7B4R1b1LljeRkG91z7SXF2JRmIuStEHB34Q3N1sE\npCDBbihaP2Uzunf9/0j7ypaAsZettQTYUPIoyIGZ/qHuaAGaA14ybOl6NstyU/BZ\n5i+RYOdvChJytfi9yLJ8UAayduLfRufoSV1LXGf1YfjtcWx/FDOtMRKLNKAzAh4Y\nPUz1Jj6PmLP25jQ5LyA70wnPVv7lYVHS2ZKvTGTng0FADBYCRg3s/gpEB91vuNWW\n9g7/BQJQMRkzaq1CGQPTlwpjV8nums5jEWJ6CQTojjOn7anwvcIw4SbSqT2/2ds3\nlefSy7nLC7B4YWQw6yMvf8OvFPEbFQsZrr8nTLtoEwIDAQABo28wbTAdBgNVHQ4E\nFgQULuQu97cGXi66GfXgZ4wCqmvlZ24wHwYDVR0jBBgwFoAULuQu97cGXi66GfXg\nZ4wCqmvlZ24wDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SH\nBH8AAAEwDQYJKoZIhvcNAQELBQADggEBADTtFpAfVI8NF2H69pMecu1ZL0XonQyi\n7tI8OetQH6ZdV3BHlyRRb5kkA7kDJSkeKHIVmL2ekB12ayU684jZtOy2eEcRML/y\nNJ0mN5+WfSUWsOhI7U+Py/LUW6o11pTC6iIoOyd2BbKd7jo/bX4XnkKgGTXVsZ4l\nD12f9IYb/DWY7SDgIiy4EsVsbEaY3Na45bZ0J8GCfWwaWbP1K8c5HRilA644CfYI\nItdcWRipChc4BPMOgIMW/9QE3OtE9J6Bcub8/QABi1Wat/bEYCpOHkMxBn/EE4hK\n90r/2fKkfFV9Zcc5RfBkXZ0kf8hHhtdYN0PbZU1tlaxinEFtHvL8hN4=\n-----END CERTIFICATE-----\n)pem\";\n\nconstexpr const char* kPrivateKeyPem = R\"pem(-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC91pDsHhHVvUuW\nN5GQb3XPtJcXYlGYi5K0QcHfhDc3WwSkIMFuKFo/ZTO6d/3/SPvKloCxl621BNhQ\n8ijIgZn+oe5oAZoDXjJs6Xo2y3JT8FnmL5Fg528KEnK1+L3IsnxQBrJ24t9G5+hJ\nXUtcZ/Vh+O1xbH8UM60xEos0oDMCHhg9TPUmPo+Ys/bmNDkvIDvTCc9W/uVhUdLZ\nkq9MZOeDQUAMFgJGDez+CkQH3W+41Zb2Dv8FAlAxGTNqrUIZA9OXCmNXye6azmMR\nYnoJBOiOM6ftqfC9wjDhJtKpPb/Z2zeV59LLucsLsHhhZDDrIy9/w68U8RsVCxmu\nvydMu2gTAgMBAAECggEAV6ceuycXLQ1+PRnjSEFusDy+Frn62uh3EGvcTIwLwq9v\n8Sh+p5JSOTtNKygESz8zo1LikR1rw009ZAKr+gh9RikWn0c+CZgQyGD1YR+G5mLv\n32zPP2MczhW+iW8Ukfp3k6vD80jFt0OU6Wr+ROhrUJVTbS+fbYB+002woNfnNW5M\nLf3N0fdmNGDXVwattzy31BV+f8JHr3grgRVTXPaT8x3ADxxlbkI26T5w8FaYx6gZ\nXE++jktNZCHB0FtgdTD9Cugm0D6WkDmu0ZYroNrk20FPswgZKqOm1/PluX5qSu0Z\n7+QfTj+6oWepB4gY1xXSb9sjWn5ZZDCVih5a/P1yQQKBgQDjYHtyi/DKM0llVpO1\nfBql217oKBVnd8GTSa7+1ehqTMuDnZMRwqFwrCQDMoMJ3nk06+Kvw4jRTn2HvUHG\nLLZhb7ZyeukvBCh3hVc3QPxy+BuoSYvt9al0ouufIZZ4hL7Yx4S3pGmRpkd4OBmQ\n9TJo2Rqjg2YLZGqxjNw3RO5kuwKBgQDVvFlYgfZlBYWKn/0Pi0XjyvQlGZYUsFdW\nh2N3L6SDoMVeYgVJbC28fDjN9O4IVXarB32JCY4bXcsHvQKSsU6chbKcXGAZPaWO\nUuniYRqmmjQyOqO2E7A5hbZMFgRxLmcmSPA505FB4WF3sPJd6W5X8i2RzFfCwAHR\nUXdrHL6AiQKBgAx2VU3J7cCnXvZ28FGaI7vDckg3KjUpkyqHd1fwUXTCEMV99Xmb\nuU17od2q/xOjZfFInHwVs4IFU0wFS32ZJcXhYZaUtgMlrzId1NHqdeu3PYzTux+n\nv0ntRAzMwnqIjA1FojiOglrBSlmEeaJATisA+zzLDuTA9DgXCFrfJFHRAoGAa8rd\n1IFW3oP2YX9mhRxcVxHYJ43L3wtAQOdvBoEEm03NvFf7CpiASHrtuxE3qwRPINpa\nOW6UOMEI0BJG5ex+FPpopesAnDo28JxoUD9gzX0freVdA0rSqXACDEVeYCZi5zAJ\n12AX9f3Qxih7U1mSyM/eo5VG/XUQdZx8eYy5luECgYBwUV7QZzTqyoEvoz3z6PAU\n++c/n+YQMrDeNg1nHjBMR5qFn4H6Eg6ZsFkSHkAXYw5PyjNF/k12rHkveTQV9kuB\nyvhREr3gAz2a/1cBXvVg8eNwQ0jkea83MZw9FK4Kf0Y9T0Eh6Vsg5sTitYrUV+l/\n/w96Ha5ea5ypiCL+Al6aYQ==\n-----END PRIVATE KEY-----\n)pem\";\n\nclass TempPemFiles\n{\npublic:\n TempPemFiles()\n {\n const auto directory = std::filesystem::temp_directory_path();\n const auto stem = \"mcpp-websocket-tls-\" + std::to_string(\n std::chrono::steady_clock::now().time_since_epoch().count());\n certificate_ = directory / (stem + \".crt.pem\");\n key_ = directory / (stem + \".key.pem\");\n write(certificate_, kCertificatePem);\n write(key_, kPrivateKeyPem);\n }\n\n ~TempPemFiles()\n {\n std::error_code ignored;\n std::filesystem::remove(certificate_, ignored);\n std::filesystem::remove(key_, ignored);\n }\n\n bool valid() const { return valid_; }\n const std::filesystem::path& certificate() const { return certificate_; }\n const std::filesystem::path& key() const { return key_; }\n\nprivate:\n void write(const std::filesystem::path& path, const char* contents)\n {\n std::ofstream out(path, std::ios::binary);\n out << contents;\n valid_ = valid_ && static_cast(out);\n }\n\n std::filesystem::path certificate_;\n std::filesystem::path key_;\n bool valid_ = true;\n};\n\nint reserve_loopback_port()\n{\n Socket socket_fd = socket(AF_INET, SOCK_STREAM, 0);\n if (socket_fd == kInvalidSocket) return -1;\n\n sockaddr_in address{};\n address.sin_family = AF_INET;\n address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n address.sin_port = 0;\n if (bind(socket_fd, reinterpret_cast(&address), sizeof(address)) != 0)\n {\n#ifdef _WIN32\n closesocket(socket_fd);\n#else\n close(socket_fd);\n#endif\n return -1;\n }\n#ifdef _WIN32\n int length = sizeof(address);\n#else\n socklen_t length = sizeof(address);\n#endif\n if (getsockname(socket_fd, reinterpret_cast(&address), &length) != 0)\n {\n#ifdef _WIN32\n closesocket(socket_fd);\n#else\n close(socket_fd);\n#endif\n return -1;\n }\n#ifdef _WIN32\n closesocket(socket_fd);\n#else\n close(socket_fd);\n#endif\n return ntohs(address.sin_port);\n}\n\n\nbool saw_open(const std::vector& messages)\n{\n for (const auto& msg : messages)\n if (msg.type == ix::WebSocketMessageType::Open) return true;\n return false;\n}\n\nbool saw_error(const std::vector& messages)\n{\n for (const auto& msg : messages)\n if (msg.type == ix::WebSocketMessageType::Error) return true;\n return false;\n}\n\n} // namespace\n\nint main()\n{\n std::cout << std::unitbuf;\n check(ix::initNetSystem(), \"ix::initNetSystem()\");\n\n TempPemFiles pem;\n check(pem.valid(), \"wrote test-only certificate and private key\");\n if (g_failures != 0)\n {\n ix::uninitNetSystem();\n return 1;\n }\n\n // Reserve a port with a raw loopback socket. The short close→listen gap is\n // retried by CI through the normal test invocation if an unrelated process\n // claims it; a fixed literal would collide systematically on shared runners.\n const int port = reserve_loopback_port();\n check(port > 0, \"reserved a loopback port\");\n if (g_failures != 0)\n {\n ix::uninitNetSystem();\n return 1;\n }\n ix::WebSocketServer server(port, \"127.0.0.1\");\n ix::SocketTLSOptions server_tls;\n server_tls.tls = true;\n server_tls.certFile = pem.certificate().string();\n server_tls.keyFile = pem.key().string();\n // The fixture authenticates the SERVER. Requesting a client certificate\n // would turn this into mTLS and reject the deliberately certificate-free\n // WebSocket client before the behavior under test can begin.\n server_tls.caFile = \"NONE\";\n server.setTLSOptions(server_tls);\n server.setOnClientMessageCallback(\n [](std::shared_ptr /*state*/,\n ix::WebSocket& socket,\n const ix::WebSocketMessagePtr& message) {\n if (message->type != ix::WebSocketMessageType::Message) return;\n if (message->binary) socket.sendBinary(message->str);\n else socket.sendText(message->str);\n });\n check(server.listenAndStart(), \"TLS WebSocketServer listenAndStart()\");\n if (g_failures != 0)\n {\n server.stop();\n ix::uninitNetSystem();\n return 1;\n }\n\n Collector collector;\n ix::WebSocket client;\n ix::SocketTLSOptions client_tls;\n client_tls.caFile = pem.certificate().string();\n // Do NOT use caFile = \"NONE\" or disable_hostname_validation: certificate\n // trust and localhost SAN matching are precisely what this test asserts.\n client.setTLSOptions(client_tls);\n client.setUrl(\"wss://localhost:\" + std::to_string(port) + \"/\");\n client.setOnMessageCallback([&](const ix::WebSocketMessagePtr& message) { collector.add(message); });\n client.start();\n\n check(collector.wait_for(saw_open, 8000), \"certificate-validated wss:// connection opened\");\n check(!collector.wait_for(saw_error, 50), \"client reported no TLS/upgrade error\");\n\n const std::string text = \"hello over WSS\";\n client.sendText(text);\n check(collector.wait_for(\n [&](const std::vector& messages) {\n for (const auto& message : messages)\n if (message.type == ix::WebSocketMessageType::Message &&\n !message.binary && message.str == text)\n return true;\n return false;\n },\n 5000),\n \"text message round-tripped over TLS\");\n\n const std::string binary(\"\\x00\\x01\\xfe\\xff\\x80WSS\", 8);\n client.sendBinary(binary);\n check(collector.wait_for(\n [&](const std::vector& messages) {\n for (const auto& message : messages)\n if (message.type == ix::WebSocketMessageType::Message &&\n message.binary && message.str == binary)\n return true;\n return false;\n },\n 5000),\n \"binary message round-tripped over TLS\");\n\n client.stop();\n server.stop();\n ix::uninitNetSystem();\n\n if (g_failures == 0)\n {\n std::cout << \"ALL WEBSOCKET-TLS ASSERTIONS PASSED\\n\";\n return 0;\n }\n std::cout << g_failures << \" ASSERTION(S) FAILED\\n\";\n return 1;\n}\n\n#else\n#error \"HAVE_WEBSOCKET_TLS must be enabled for every declared test platform\"\n#endif\n" + } + ] + }, + { + "project": "websocket", + "text": "# websocket test project: consumes compat.websocket and asserts client behavior\n# under `mcpp test`. Part of the mcpp-index self-referential workspace — the\n# workspace-root `[indices]` redirect points at this repo, so the dependency\n# resolves to the checked-in recipe (pkgs/c/compat.websocket.lua).\n#\n# The test is entirely OFFLINE and self-contained: it runs a minimal RFC 6455\n# echo server inside the test process (raw loopback sockets) and drives the\n# ix::WebSocket client against it, so it does not depend on the CI runner\n# having (or not having) network access or a WS server process.\n[package]\nname = \"websocket-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nwebsocket = \"12.0.1\"\n", + "files": [ + { + "path": "tests/examples/websocket/tests/ws_test.cpp", + "text": "// ws_test.cpp — self-contained WebSocket client test for compat.websocket.\n//\n// IXWebSocket is a pure client here (the descriptor builds the client TUs\n// only), so this test brings its OWN minimal RFC 6455 echo server, built on\n// raw sockets, and drives the ix::WebSocket client against 127.0.0.1:0. That\n// exercises the real wire behaviors the client must implement — the HTTP\n// upgrade handshake (the client validates Sec-WebSocket-Accept), client-side\n// masking, fragmentation (the server replies with a fragmented message and the\n// client must reassemble it), ping/pong in both directions, and the closing\n// handshake. No network access and no server process are required: everything\n// stays inside one process on the loopback interface.\n//\n// The server is intentionally independent of the library under test: it\n// computes Sec-WebSocket-Accept with its own SHA-1 + base64 and implements the\n// framing itself, so a client bug in any of those areas fails the assertions\n// below rather than being hidden by shared code.\n\n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nnamespace\n{\n\nint g_failures = 0;\n\nvoid check(bool ok, const std::string& what)\n{\n if (ok)\n {\n std::cout << \" ok: \" << what << \"\\n\";\n }\n else\n {\n std::cout << \" FAIL: \" << what << \"\\n\";\n ++g_failures;\n }\n}\n\n// ── SHA-1 and base64 (for Sec-WebSocket-Accept) ────────────────────────────\n// Compact reference implementations, independent of the library under test.\n\nstd::string base64_encode(const std::string& in)\n{\n static const char tbl[] =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n std::string out;\n unsigned val = 0;\n int valb = -6;\n for (unsigned char c : in)\n {\n val = (val << 8) | c;\n valb += 8;\n while (valb >= 0)\n {\n out.push_back(tbl[(val >> valb) & 0x3F]);\n valb -= 6;\n }\n }\n if (valb > -6) out.push_back(tbl[((val << 8) >> (valb + 8)) & 0x3F]);\n while (out.size() % 4) out.push_back('=');\n return out;\n}\n\nstd::string sha1_binary(const std::string& msg)\n{\n uint32_t h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476,\n h4 = 0xC3D2E1F0;\n uint64_t bitlen = static_cast(msg.size()) * 8;\n std::string m = msg;\n m.push_back(static_cast(0x80));\n while (m.size() % 64 != 56) m.push_back('\\0');\n for (int i = 7; i >= 0; --i)\n m.push_back(static_cast((bitlen >> (i * 8)) & 0xFF));\n\n for (size_t i = 0; i < m.size(); i += 64)\n {\n uint32_t w[80];\n for (int j = 0; j < 16; ++j)\n w[j] = (static_cast(static_cast(m[i + j * 4])) << 24) |\n (static_cast(static_cast(m[i + j * 4 + 1])) << 16) |\n (static_cast(static_cast(m[i + j * 4 + 2])) << 8) |\n (static_cast(static_cast(m[i + j * 4 + 3])));\n for (int j = 16; j < 80; ++j)\n {\n uint32_t x = w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16];\n w[j] = (x << 1) | (x >> 31);\n }\n uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;\n for (int j = 0; j < 80; ++j)\n {\n uint32_t f, k;\n if (j < 20)\n {\n f = (b & c) | ((~b) & d);\n k = 0x5A827999;\n }\n else if (j < 40)\n {\n f = b ^ c ^ d;\n k = 0x6ED9EBA1;\n }\n else if (j < 60)\n {\n f = (b & c) | (b & d) | (c & d);\n k = 0x8F1BBCDC;\n }\n else\n {\n f = b ^ c ^ d;\n k = 0xCA62C1D6;\n }\n uint32_t tmp = ((a << 5) | (a >> 27)) + f + e + k + w[j];\n e = d;\n d = c;\n c = (b << 30) | (b >> 2);\n b = a;\n a = tmp;\n }\n h0 += a;\n h1 += b;\n h2 += c;\n h3 += d;\n h4 += e;\n }\n std::string out(20, '\\0');\n const uint32_t hs[5] = {h0, h1, h2, h3, h4};\n for (int i = 0; i < 5; ++i)\n for (int j = 0; j < 4; ++j)\n out[i * 4 + j] = static_cast((hs[i] >> ((3 - j) * 8)) & 0xFF);\n return out;\n}\n\n// ── Platform socket abstraction ────────────────────────────────────────────\n\n#ifdef _WIN32\n#include \n#include \nusing SockType = SOCKET;\nconstexpr SockType kInvalidSocket = INVALID_SOCKET;\nusing AddrLenType = int;\n#else\n#include \n#include \n#include \n#include \n#include \nusing SockType = int;\nconstexpr SockType kInvalidSocket = -1;\nusing AddrLenType = socklen_t;\n#endif\n\nvoid init_sockets()\n{\n#ifdef _WIN32\n WSADATA wsa;\n WSAStartup(MAKEWORD(2, 2), &wsa);\n#endif\n}\n\nvoid close_socket(SockType s)\n{\n#ifdef _WIN32\n closesocket(s);\n#else\n ::close(s);\n#endif\n}\n\n// Safety net: any read that stalls this long makes the server thread return so\n// the test fails cleanly instead of hanging the process (and, on CI, a runner).\nvoid set_recv_timeout(SockType s, int ms)\n{\n#ifdef _WIN32\n DWORD t = static_cast(ms);\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&t), sizeof(t));\n#else\n timeval tv{};\n tv.tv_sec = ms / 1000;\n tv.tv_usec = (ms % 1000) * 1000;\n setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));\n#endif\n}\n\n// ── Minimal RFC 6455 echo server ───────────────────────────────────────────\n\nclass EchoServer\n{\npublic:\n ~EchoServer()\n {\n if (client != kInvalidSocket) close_socket(client);\n if (listener != kInvalidSocket) close_socket(listener);\n if (thread.joinable()) thread.join();\n }\n\n bool start()\n {\n init_sockets();\n listener = socket(AF_INET, SOCK_STREAM, 0);\n if (listener == kInvalidSocket) return false;\n int one = 1;\n#ifdef _WIN32\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,\n reinterpret_cast(&one), sizeof(one));\n#else\n setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));\n#endif\n sockaddr_in addr{};\n addr.sin_family = AF_INET;\n addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n addr.sin_port = 0; // ephemeral\n if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0) return false;\n AddrLenType alen = sizeof(addr);\n getsockname(listener, reinterpret_cast(&addr), &alen);\n port = ntohs(addr.sin_port);\n if (listen(listener, 1) != 0) return false;\n thread = std::thread([this] { run(); });\n return true;\n }\n\n int port = 0;\n std::atomic server_saw_masked{false};\n std::atomic server_saw_pong{false};\n std::atomic server_saw_close{false};\n\nprivate:\n SockType listener = kInvalidSocket;\n SockType client = kInvalidSocket;\n std::thread thread;\n\n // Receive exactly n bytes; false on timeout/error/disconnect.\n bool recv_n(char* buf, size_t n)\n {\n size_t got = 0;\n while (got < n)\n {\n int r = recv(client, buf + got, static_cast(n - got), 0);\n if (r <= 0) return false;\n got += static_cast(r);\n }\n return true;\n }\n\n std::string lower(std::string s)\n {\n for (char& c : s)\n if (c >= 'A' && c <= 'Z') c = static_cast(c + ('a' - 'A'));\n return s;\n }\n\n bool handshake()\n {\n std::string req;\n char tmp[1024];\n while (req.find(\"\\r\\n\\r\\n\") == std::string::npos)\n {\n int r = recv(client, tmp, sizeof(tmp), 0);\n if (r <= 0) return false;\n req.append(tmp, static_cast(r));\n if (req.size() > 65536) return false;\n }\n std::string key;\n std::string rest = req;\n for (;;)\n {\n size_t eol = rest.find(\"\\r\\n\");\n if (eol == std::string::npos) break;\n std::string line = rest.substr(0, eol);\n rest = rest.substr(eol + 2);\n size_t colon = line.find(':');\n if (colon == std::string::npos) continue;\n std::string name = lower(line.substr(0, colon));\n std::string value = line.substr(colon + 1);\n size_t b = value.find_first_not_of(\" \\t\");\n size_t e = value.find_last_not_of(\" \\t\");\n value = (b == std::string::npos) ? std::string() : value.substr(b, e - b + 1);\n if (name == \"sec-websocket-key\") key = value;\n }\n if (key.empty()) return false;\n\n const std::string guid = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n std::string accept = base64_encode(sha1_binary(key + guid));\n\n std::string resp = \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n \"Upgrade: websocket\\r\\n\"\n \"Connection: Upgrade\\r\\n\"\n \"Sec-WebSocket-Accept: \" + accept + \"\\r\\n\"\n \"\\r\\n\";\n size_t off = 0;\n while (off < resp.size())\n {\n int w = send(client, resp.data() + off, static_cast(resp.size() - off), 0);\n if (w <= 0) return false;\n off += static_cast(w);\n }\n return true;\n }\n\n bool send_frame(uint8_t opcode, const std::string& payload, bool fin = true)\n {\n // Server->client frames are NOT masked (RFC 6455 §5.1).\n std::string frame;\n frame.push_back(static_cast((fin ? 0x80 : 0x00) | (opcode & 0x0F)));\n size_t n = payload.size();\n if (n < 126)\n {\n frame.push_back(static_cast(n));\n }\n else if (n <= 0xFFFF)\n {\n frame.push_back(static_cast(126));\n frame.push_back(static_cast((n >> 8) & 0xFF));\n frame.push_back(static_cast(n & 0xFF));\n }\n else\n {\n frame.push_back(static_cast(127));\n for (int i = 7; i >= 0; --i)\n frame.push_back(static_cast((n >> (i * 8)) & 0xFF));\n }\n frame += payload;\n size_t off = 0;\n while (off < frame.size())\n {\n int w = send(client, frame.data() + off, static_cast(frame.size() - off), 0);\n if (w <= 0) return false;\n off += static_cast(w);\n }\n return true;\n }\n\n bool recv_frame(uint8_t& opcode, std::string& payload)\n {\n char hdr[2];\n if (!recv_n(hdr, 2)) return false;\n bool fin = (hdr[0] & 0x80) != 0;\n (void) fin;\n opcode = static_cast(hdr[0] & 0x0F);\n bool masked = (hdr[1] & 0x80) != 0;\n if (masked) server_saw_masked = true;\n size_t len = static_cast(hdr[1] & 0x7F);\n if (len == 126)\n {\n char ext[2];\n if (!recv_n(ext, 2)) return false;\n len = (static_cast(static_cast(ext[0])) << 8) |\n static_cast(static_cast(ext[1]));\n }\n else if (len == 127)\n {\n char ext[8];\n if (!recv_n(ext, 8)) return false;\n len = 0;\n for (int i = 0; i < 8; ++i)\n len = (len << 8) | static_cast(static_cast(ext[i]));\n }\n char key[4] = {0, 0, 0, 0};\n if (masked && !recv_n(key, 4)) return false;\n payload.resize(len);\n if (len && !recv_n(&payload[0], len)) return false;\n if (masked)\n for (size_t i = 0; i < len; ++i)\n payload[i] = static_cast(payload[i] ^ key[i % 4]);\n return true;\n }\n\n void run()\n {\n sockaddr_in peer{};\n AddrLenType plen = sizeof(peer);\n client = accept(listener, reinterpret_cast(&peer), &plen);\n if (client == kInvalidSocket) return;\n set_recv_timeout(client, 10000);\n if (!handshake()) return;\n\n for (;;)\n {\n uint8_t opcode = 0;\n std::string payload;\n if (!recv_frame(opcode, payload)) return;\n\n switch (opcode)\n {\n case 0x9: // ping -> pong\n if (!send_frame(0xA, payload)) return;\n break;\n case 0xA: // pong (answer to our ping)\n server_saw_pong = true;\n break;\n case 0x8: // close -> echo close code, then shut down\n {\n server_saw_close = true;\n std::string close_payload = payload;\n if (close_payload.size() < 2) close_payload = std::string(\"\\x03\\xe8\", 2); // 1000\n if (!send_frame(0x8, close_payload)) return;\n return;\n }\n case 0x1: // text\n case 0x2: // binary\n {\n if (payload == \"FRAG\")\n {\n // Reply with a fragmented text message.\n if (!send_frame(0x1, \"fragA-\", false)) return;\n if (!send_frame(0x0, \"fragB-\", false)) return;\n if (!send_frame(0x0, \"fragC!\", true)) return;\n }\n else if (payload == \"PINGME\")\n {\n // Ask the client to answer a ping with a pong.\n if (!send_frame(0x9, \"server-ping\", true)) return;\n }\n else\n {\n if (!send_frame(opcode, payload, true)) return;\n }\n break;\n }\n default:\n // continuation frames and anything else: ignore.\n break;\n }\n }\n }\n};\n\n// ── Client-side message collector ──────────────────────────────────────────\n\nstruct RecvMsg\n{\n ix::WebSocketMessageType type;\n std::string str;\n bool binary;\n};\n\nclass Collector\n{\npublic:\n void add(const ix::WebSocketMessagePtr& msg)\n {\n std::lock_guard lk(m_);\n msgs_.push_back(RecvMsg{msg->type, msg->str, msg->binary});\n cv_.notify_all();\n }\n\n // Wait up to timeout_ms for a predicate over the collected messages.\n template \n bool wait_for(Pred pred, int timeout_ms)\n {\n std::unique_lock lk(m_);\n return cv_.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return pred(msgs_); });\n }\n\n bool has_type(ix::WebSocketMessageType t) const\n {\n std::lock_guard lk(m_);\n for (const auto& m : msgs_)\n if (m.type == t) return true;\n return false;\n }\n\nprivate:\n mutable std::mutex m_;\n std::condition_variable cv_;\n std::vector msgs_;\n};\n\n} // namespace\n\nint main()\n{\n // Unbuffered: `mcpp test` runs this piped, and a hang without visible\n // progress is indistinguishable from a slow pass otherwise.\n std::cout << std::unitbuf;\n\n EchoServer server;\n if (!server.start())\n {\n std::cout << \"FAIL: could not start echo server\\n\";\n return 1;\n }\n std::cout << \"server listening on 127.0.0.1:\" << server.port << \"\\n\";\n\n bool netOk = ix::initNetSystem();\n check(netOk, \"ix::initNetSystem()\");\n\n Collector collector;\n ix::WebSocket ws;\n ws.setUrl(\"ws://127.0.0.1:\" + std::to_string(server.port) + \"/\");\n ws.setOnMessageCallback([&](const ix::WebSocketMessagePtr& msg) { collector.add(msg); });\n ws.start();\n\n // 1. Handshake: the client must validate our Sec-WebSocket-Accept and open.\n check(collector.wait_for(\n [](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Open) return true;\n return false;\n },\n 5000),\n \"client opened after HTTP upgrade handshake\");\n\n // 2. Text echo — by the time the server echoes, it has seen the client's\n // masked frames (RFC 6455 §5.1 requires client->server masking).\n ws.sendText(\"hello, ws!\");\n check(collector.wait_for(\n [](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Message && m.str == \"hello, ws!\" &&\n !m.binary)\n return true;\n return false;\n },\n 5000),\n \"text message echoed back\");\n check(server.server_saw_masked.load(), \"client frames arrive masked\");\n\n // 3. Binary echo (arbitrary bytes, not valid UTF-8).\n std::string bin(\"\\x00\\x01\\x02\\xfe\\xff\\x80hello\", 9);\n ws.sendBinary(bin);\n check(collector.wait_for(\n [&](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Message && m.str == bin && m.binary)\n return true;\n return false;\n },\n 5000),\n \"binary message echoed back byte-for-byte\");\n\n // 4. Client ping -> server pong -> client surfaces the Pong message.\n ws.ping(\"ping-payload\");\n check(collector.wait_for(\n [](const std::vector& v) {\n for (const auto& m : v)\n if (m.type == ix::WebSocketMessageType::Pong && m.str == \"ping-payload\")\n return true;\n return false;\n },\n 5000),\n \"ping/pong round-trip (client-initiated)\");\n\n // 5. Server ping -> client auto-pong (enablePong default is on).\n ws.sendText(\"PINGME\");\n {\n auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);\n while (!server.server_saw_pong.load() && std::chrono::steady_clock::now() < deadline)\n std::this_thread::sleep_for(std::chrono::milliseconds(10));\n }\n check(server.server_saw_pong.load(), \"client auto-answered a server ping with a pong\");\n\n // 6. Fragmentation: server replies with a fragmented text message, the\n // client must reassemble it into one Message (Fragment frames precede it).\n ws.sendText(\"FRAG\");\n check(collector.wait_for(\n [&](const std::vector& v) {\n bool sawFragment = false;\n for (const auto& m : v)\n {\n if (m.type == ix::WebSocketMessageType::Fragment) sawFragment = true;\n if (m.type == ix::WebSocketMessageType::Message && m.str == \"fragA-fragB-fragC!\")\n return true;\n }\n (void) sawFragment;\n return false;\n },\n 5000),\n \"fragmented server message reassembled by the client\");\n check(collector.has_type(ix::WebSocketMessageType::Fragment),\n \"fragment frames surfaced before the reassembled message\");\n\n // 7. Close handshake: stop() initiates the close (via its internal\n // close()), waits for the server's close reply and for the client thread\n // to exit, and sets `_stop` so the run loop cannot fall into the\n // automatic reconnection that a CLOSED state would otherwise trigger\n // (IXWebSocket enables automatic reconnection by default). The Close\n // message is delivered to the collector before stop() returns.\n ws.stop();\n check(collector.has_type(ix::WebSocketMessageType::Close),\n \"closing handshake completed (client saw Close)\");\n check(server.server_saw_close.load(), \"server received the client's close frame\");\n\n ix::uninitNetSystem();\n\n if (g_failures == 0)\n {\n std::cout << \"ALL WEBSOCKET ASSERTIONS PASSED\\n\";\n return 0;\n }\n std::cout << g_failures << \" ASSERTION(S) FAILED\\n\";\n return 1;\n}\n" + } + ] + }, + { + "project": "wil", + "text": "# WIL test project. The library is Windows-only — not a portable library with a\n# Windows backend, but a library ABOUT Win32 — so the dependency is gated and\n# the test compiles to a no-op main() elsewhere, the same shape gui-stack uses\n# in the other direction.\n[package]\nname = \"wil-tests\"\nversion = \"0.1.0\"\n\n[target.'cfg(windows)'.dependencies.compat]\nwil = \"1.0.260126.7\"\n\n[target.'cfg(windows)'.build]\nldflags = [\"-lole32\", \"-loleaut32\"]\n", + "files": [ + { + "path": "tests/examples/wil/tests/raii.cpp", + "text": "// Behavioral test: WIL's three families, each doing real work.\n//\n// A header-only package can fail in two ways a \"does it link\" test would miss:\n// the headers might not compile under this toolchain at all (WIL is dense in\n// __declspec, SAL annotations and intrinsics, and this index builds Windows\n// with clang rather than cl.exe), and the RAII types might not actually release\n// what they own. So the assertions observe SIDE EFFECTS — a handle that really\n// closed, a COM refcount that really dropped — rather than just naming symbols.\n#ifdef _WIN32\n\n#include \n#include \n#include \n\n#include \n#include \n\n#include \n#include \n\nnamespace {\n\n// Returns whether the handle value still names a live kernel object in this\n// process. This is how \"the wrapper really closed it\" gets observed.\nbool handle_is_live(HANDLE h) {\n DWORD flags = 0;\n return ::GetHandleInformation(h, &flags) != FALSE;\n}\n\nHRESULT succeeds() { return S_OK; }\nHRESULT fails() { return E_ACCESSDENIED; }\n\nHRESULT propagates_failure() {\n RETURN_IF_FAILED(succeeds());\n RETURN_IF_FAILED(fails()); // must return here\n return S_FALSE; // unreachable\n}\n\n} // namespace\n\nint main() {\n // ── wil::unique_handle: ownership, move, and actual closure ──────────\n HANDLE raw = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);\n assert(raw != nullptr && handle_is_live(raw));\n {\n wil::unique_handle owner(raw);\n assert(owner.get() == raw);\n\n wil::unique_handle moved = std::move(owner);\n assert(!owner && moved.get() == raw); // move really transferred\n assert(handle_is_live(raw)); // and did not close on the way\n }\n assert(!handle_is_live(raw) && \"unique_handle did not close the handle\");\n\n // release() must hand ownership BACK, i.e. not close it\n HANDLE raw2 = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);\n assert(raw2 != nullptr);\n {\n wil::unique_handle owner(raw2);\n HANDLE released = owner.release();\n assert(released == raw2 && !owner);\n }\n assert(handle_is_live(raw2) && \"release() must not close\");\n ::CloseHandle(raw2);\n\n // ── wil::unique_hlocal_string over a LocalAlloc'd buffer ─────────────\n {\n auto* buffer = static_cast(::LocalAlloc(LPTR, 8 * sizeof(wchar_t)));\n assert(buffer != nullptr);\n wcscpy_s(buffer, 8, L\"wil\");\n wil::unique_hlocal_string owned(buffer);\n assert(owned && wcscmp(owned.get(), L\"wil\") == 0);\n } // LocalFree runs here; nothing observable, but it must compile and not crash\n\n // ── wil::com_ptr against a real COM object ───────────────────────────\n {\n const auto uninit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED);\n\n wil::com_ptr link;\n const HRESULT hr = ::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,\n IID_PPV_ARGS(&link));\n assert(SUCCEEDED(hr) && link);\n\n // query() crosses an interface — the classic com_ptr operation.\n auto persist = link.query();\n assert(persist);\n\n // A raw AddRef/Release pair around a copy proves the smart pointer is\n // actually counting, not just holding.\n link->AddRef();\n const ULONG after_addref = link->AddRef();\n const ULONG after_release = link->Release();\n assert(after_release == after_addref - 1);\n link->Release();\n\n wil::com_ptr copy = link;\n assert(copy.get() == link.get());\n copy.reset();\n assert(!copy && link);\n }\n\n // ── HRESULT macros: the error model, not just the spelling ───────────\n assert(propagates_failure() == E_ACCESSDENIED);\n\n bool threw = false;\n try {\n THROW_IF_FAILED(fails());\n } catch (const wil::ResultException& e) {\n threw = true;\n assert(e.GetErrorCode() == E_ACCESSDENIED);\n }\n assert(threw && \"THROW_IF_FAILED did not throw on a failed HRESULT\");\n\n // …and must NOT throw on success.\n THROW_IF_FAILED(succeeds());\n\n return 0;\n}\n\n#else\n\nint main() { return 0; } // WIL is Windows-only; nothing to assert here.\n\n#endif\n" + } + ] + }, + { + "project": "wlroots", + "text": "# wlroots — the compositor library, consumed the way a compositor would.\n#\n# ⚠️ The `wlroots` index redirect is what makes this test the INDEX's rather\n# than the published package's. It has to be keyed on the namespace the\n# dependency below requests: a redirect declared under a name nothing asks for\n# is silently not registered, and the resolution falls back to the remote\n# index — at which point this checks a package that is already published\n# rather than the descriptor in this PR.\n[indices]\nwlroots = { path = \"../../..\" }\n\n[package]\nname = \"wlroots-tests\"\nversion = \"0.1.0\"\nstandard = \"c++23\"\n\n[target.'cfg(linux)'.dependencies.wlroots]\nwlroots = \"0.20.2\"\n", + "files": [ + { + "path": "tests/examples/wlroots/tests/wlroots.cpp", + "text": "// wlroots — what can be asserted without a seat, a GPU or a compositor.\n//\n// WHAT THIS TEST IS FOR\n//\n// A compositor library is mostly untestable in CI: creating a backend wants a\n// DRM master, a libseat session and an input device, and none of a runner's\n// three exist. So the temptation is to assert that it linked, which is worth\n// almost nothing — a package can link and still have compiled none of the\n// files a consumer needs (this index has measured exactly that).\n//\n// Everything below is chosen to be an INTERMEDIATE QUANTITY: a value the\n// library computes on its own, from data it carries, with no device involved.\n// Each one names a specific generator in build.mcpp, so a failure says which\n// one broke rather than \"wlroots is broken\":\n//\n// version → include/wlr/version.h\n// WLR_HAS_* → include/wlr/config.h, and the feature set behind it\n// the PNP table → pnpids.c, from the pinned hwdata\n// the box maths → util/box.c, i.e. that ordinary sources compiled\n// the module itself → 986 `using ::` declarations that must all resolve\n// `namespace_` → the C++ keyword-member rewrite\n//\n// ⚠️ NO extern \"C\" WRAPPER, and that is the point. wlroots' public headers\n// contain not one `extern \"C\"` block, and two of them are not even valid C++\n// (C99 `[static N]` array parameters). A C++ consumer cannot include them at\n// all. `import wlroots;` is not a nicety here — it is the only way in.\n\n#ifdef __linux__\n\n// ⚠️ THE MODULE CANNOT CARRY MACROS, so `WLR_HAS_*` comes from the header.\n// Macros are preprocessor entities and nothing exports them.\n//\n// ⚠️ AND ONLY A MACRO-ONLY HEADER MAY BE INCLUDED ALONGSIDE THE MODULE.\n// `` is nothing but `#define`s, so it is safe. ``\n// is not: it DECLARES wlr_version_get_major and has no `extern \"C\"` of its\n// own, so including it here gives those three names C++ linkage while the\n// module's have C linkage — two different entities, and the link fails with\n//\n// undefined reference to `wlr_version_get_major()'\n//\n// naming a function the library plainly contains. The parentheses in that\n// message are the tell: a mangled name, i.e. the wrong one. Take declarations\n// from the module and macros from the header, never the same name from both.\n#include \n\nimport wlroots;\n\n// `get_pnp_manufacturer` is INTERNAL to the DRM backend — declared in the\n// private `backend/drm/util.h`, so it is not in the module and not in any\n// installed header. Declared here because the generated table behind it is\n// the one thing in this package whose failure looks exactly like success.\n// `const char code[static 3]` and `const char *` are the same parameter after\n// array-to-pointer decay, so this declaration is ABI-identical to upstream's.\nextern \"C\" const char *get_pnp_manufacturer(const char *code);\n\n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool ok, const char *what)\n{\n std::printf(\"%-62s %s\\n\", what, ok ? \"ok\" : \"FAILED\");\n if (!ok) {\n ++failures;\n }\n}\n\n} // namespace\n\nint main()\n{\n // ── 1. version.h was generated, and says what the manifest says ───────\n std::printf(\" wlroots %d.%d.%d\\n\", wlr_version_get_major(),\n wlr_version_get_minor(), wlr_version_get_micro());\n check(wlr_version_get_major() == 0 && wlr_version_get_minor() == 20,\n \"version.h reports 0.20, the version the manifest declares\");\n\n // ── 2. config.h reports THIS build's features ────────────────────────\n // Not \"some features are on\" — the exact default set, so a feature that\n // silently stopped being compiled is a failure and not a shrug.\n std::printf(\" drm=%d libinput=%d session=%d gles2=%d gbm=%d | \"\n \"x11=%d vulkan=%d xwayland=%d\\n\",\n WLR_HAS_DRM_BACKEND, WLR_HAS_LIBINPUT_BACKEND, WLR_HAS_SESSION,\n WLR_HAS_GLES2_RENDERER, WLR_HAS_GBM_ALLOCATOR,\n WLR_HAS_X11_BACKEND, WLR_HAS_VULKAN_RENDERER, WLR_HAS_XWAYLAND);\n check(WLR_HAS_DRM_BACKEND && WLR_HAS_LIBINPUT_BACKEND && WLR_HAS_SESSION\n && WLR_HAS_GLES2_RENDERER && WLR_HAS_GBM_ALLOCATOR,\n \"the five default features are all on\");\n check(!WLR_HAS_X11_BACKEND && !WLR_HAS_VULKAN_RENDERER && !WLR_HAS_XWAYLAND,\n \"…and X11, Vulkan and Xwayland are off, as the manifest says\");\n\n // ── 3. util/box.c: ordinary sources really were compiled ─────────────\n // Pure arithmetic on two rectangles, no device. If the package had linked\n // without compiling anything this would not resolve.\n wlr_box a{.x = 0, .y = 0, .width = 100, .height = 50};\n wlr_box b{.x = 60, .y = 20, .width = 100, .height = 50};\n wlr_box out{};\n const bool overlap = wlr_box_intersection(&out, &a, &b);\n std::printf(\" intersection = %d,%d %dx%d\\n\", out.x, out.y, out.width, out.height);\n check(overlap && out.x == 60 && out.y == 20 && out.width == 40 && out.height == 30,\n \"wlr_box_intersection computes the overlap\");\n\n check(wlr_box_contains_point(&a, 10, 10) && !wlr_box_contains_point(&a, 10, 90),\n \"wlr_box_contains_point agrees with the box it was given\");\n\n // ── 4. the scene graph allocates ─────────────────────────────────────\n // wlr_scene_create needs no backend — it is the one non-trivial object a\n // test can build. It also proves types/scene/*.c compiled.\n wlr_scene *scene = wlr_scene_create();\n check(scene != nullptr, \"wlr_scene_create without a backend\");\n if (scene != nullptr) {\n const float colour[4] = {1.0f, 0.0f, 0.0f, 1.0f};\n wlr_scene_rect *rect = wlr_scene_rect_create(&scene->tree, 32, 16, colour);\n check(rect != nullptr, \"…and a rect in it\");\n // wlr_scene_rect_set_color is one of the five declarations that use\n // C99 `[static 4]`. Calling it is what proves the rewritten header\n // declares the same function the library defines.\n if (rect != nullptr) {\n const float other[4] = {0.0f, 1.0f, 0.0f, 1.0f};\n wlr_scene_rect_set_color(rect, other);\n check(true, \"…and wlr_scene_rect_set_color, whose C99 [static 4] was rewritten\");\n }\n wlr_scene_node_destroy(&scene->tree.node);\n }\n\n // ── 5. the C++ keyword-member rewrite ────────────────────────────────\n // `struct wlr_layer_surface_v1` has a member C++ cannot name. The header\n // this build compiles against renames it under `#ifdef __cplusplus`, at\n // the same offset. Reading it through a null-object offset would be UB, so\n // this asserts on a real object's storage instead.\n wlr_layer_surface_v1 ls{};\n ls.namespace_ = const_cast(\"panel\");\n check(std::strcmp(ls.namespace_, \"panel\") == 0,\n \"wlr_layer_surface_v1::namespace_ is reachable from C++\");\n\n // ── 6. the PNP table generated from the PINNED hwdata ────────────────\n // The failure this catches looks exactly like a passing build: upstream's\n // gen_pnpids.sh reads /usr/share/hwdata/pnp.ids off the BUILD MACHINE, so\n // the manufacturer a monitor reports would depend on where the package was\n // built. `get_pnp_manufacturer` is wlroots-internal rather than exported,\n // so it is reached through the DRM backend header's declaration.\n //\n // \"ACR\" is the id in the EDID of the Acer monitor freedesktop.libdisplay-\n // info tests against, which is where the expected string comes from.\n const char *acr = get_pnp_manufacturer(\"ACR\");\n const char *del = get_pnp_manufacturer(\"DEL\");\n // ⚠️ \"JJJ\", not an arbitrary byte triple. PNP_ID keeps only the LOW FIVE\n // BITS of each character, so \"\\x01\\x02\\x03\" packs to the same key as \"ABC\"\n // and returns AboCom System Inc. — measured, and it is upstream's encoding\n // rather than a bug here. An id that is genuinely absent has to be one\n // whose 15-bit key no entry occupies; \"JJJ\" (key 10570) is one of them.\n const char *nope = get_pnp_manufacturer(\"JJJ\");\n std::printf(\" ACR=%s DEL=%s JJJ=%s\\n\", acr ? acr : \"(null)\",\n del ? del : \"(null)\", nope ? nope : \"(null)\");\n check(acr != nullptr && std::strstr(acr, \"Acer\") != nullptr,\n \"the pinned pnp.ids resolved ACR to Acer\");\n check(del != nullptr && std::strstr(del, \"Dell\") != nullptr, \"…and DEL to Dell\");\n check(nope == nullptr, \"…and an unoccupied key returns NULL, not a neighbour\");\n\n std::printf(\"\\n%d check(s) failed\\n\", failures);\n return failures == 0 ? 0 : 1;\n}\n\n#else\nint main() { return 0; }\n#endif\n" + } + ] + }, + { + "project": "xxhash", + "text": "# xxHash test project: consumes compat.xxhash from this repo's own index and\n# asserts the hash values themselves, not just that the symbols link.\n[package]\nname = \"xxhash-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nxxhash = \"0.8.3\"\n", + "files": [ + { + "path": "tests/examples/xxhash/tests/digest.cpp", + "text": "// Behavioral test: the digests themselves, against upstream's own vectors.\n//\n// A hash package that links but computes a different value is worse than one\n// that does not link at all — every caller downstream is content-addressing\n// something. So the assertions here are known-answer tests taken verbatim from\n// xxHash's `tests/sanity_test_vectors.h`, together with the buffer generator\n// from `tests/sanity_test.c` that those vectors were computed over. Nothing is\n// derived from running the code under test.\n//\n// The lengths cover every size branch XXH3 switches on (0, 1-3, 4-8, 9-16,\n// 17-128, 129-240, >240) plus the multi-block path at 2048 and 4160, which is\n// where a mis-selected SIMD variant or a byte-order slip would show up.\n#include \n\n#include \n#include \n#include \n#include \n\nnamespace {\n\n// tests/sanity_test.c: fillTestBuffer(). \"Its values must not be changed.\"\nconstexpr std::uint32_t kPrime32 = 2654435761U;\nconstexpr std::uint64_t kPrime64 = 11400714785074694797ULL;\n\nstd::vector make_sanity_buffer(std::size_t len) {\n std::vector buf(len);\n std::uint64_t gen = kPrime32;\n for (std::size_t i = 0; i < len; ++i) {\n buf[i] = static_cast(gen >> 56);\n gen *= kPrime64;\n }\n return buf;\n}\n\nstruct Case32 { std::uint32_t len; std::uint32_t seed; std::uint32_t expected; };\nstruct Case64 { std::uint32_t len; std::uint64_t seed; std::uint64_t expected; };\nstruct Case128 { std::uint32_t len; std::uint64_t lo; std::uint64_t hi; };\n\n// XSUM_XXH32_testdata, seed 0.\nconstexpr Case32 kXXH32[] = {\n { 0, 0x00000000U, 0x02CC5D05U }, { 1, 0x00000000U, 0xCF65B03EU },\n { 2, 0x00000000U, 0x1151BEE4U }, { 3, 0x00000000U, 0xC23884F5U },\n { 4, 0x00000000U, 0xA9DE7CE9U }, { 8, 0x00000000U, 0xA3F6F44BU },\n { 9, 0x00000000U, 0xFFB82A24U }, { 16, 0x00000000U, 0x93BA3759U },\n { 17, 0x00000000U, 0x89FDC23EU }, { 64, 0x00000000U, 0x02E95DBBU },\n { 128, 0x00000000U, 0x0FD07B71U }, { 129, 0x00000000U, 0x68C9EC37U },\n { 222, 0x00000000U, 0x5BD11DBDU }, { 240, 0x00000000U, 0xFA6B6557U },\n { 241, 0x00000000U, 0xE5F7C54DU }, { 403, 0x00000000U, 0x6675FF5AU },\n { 512, 0x00000000U, 0xD485C30AU }, { 2048, 0x00000000U, 0x7C535464U },\n { 4160, 0x00000000U, 0xDB0D9141U },\n};\n\n// XSUM_XXH64_testdata, seed 0.\nconstexpr Case64 kXXH64[] = {\n { 0, 0ULL, 0xEF46DB3751D8E999ULL }, { 1, 0ULL, 0xE934A84ADB052768ULL },\n { 2, 0ULL, 0x5D48CD60A77E23FFULL }, { 3, 0ULL, 0xFF7E1959CB50794AULL },\n { 4, 0ULL, 0x9136A0DCA57457EEULL }, { 8, 0ULL, 0xCDBCF538E71D1348ULL },\n { 9, 0ULL, 0x554B1AE991EDA6B6ULL }, { 16, 0ULL, 0x98C90B57FDFCB55CULL },\n { 17, 0ULL, 0x0D39A2D051A30C2CULL }, { 64, 0ULL, 0xEF558F8ACAC2B5CDULL },\n { 128, 0ULL, 0x90CA021457D96DC5ULL }, { 129, 0ULL, 0x41C280132D697ABAULL },\n { 222, 0ULL, 0xB641AE8CB691C174ULL }, { 240, 0ULL, 0xB81838D483BAEE53ULL },\n { 241, 0ULL, 0x95D76C8B4D8FC4D6ULL }, { 403, 0ULL, 0xD99858FEE82283DFULL },\n { 512, 0ULL, 0x4358D2FDD62B58A7ULL }, { 2048, 0ULL, 0x5940F2752BC04387ULL },\n { 4160, 0ULL, 0xEEE6A4E2AC952A5EULL },\n};\n\n// XSUM_XXH3_testdata, seed 0 and seed PRIME64 — the seeded path is a different\n// branch in XXH3, not a reseeding of the same one.\nconstexpr Case64 kXXH3[] = {\n { 0, 0ULL, 0x2D06800538D394C2ULL }, { 1, 0ULL, 0xC44BDFF4074EECDBULL },\n { 2, 0ULL, 0x7A9978044CB8A8BBULL }, { 3, 0ULL, 0x54247382A8D6B94DULL },\n { 4, 0ULL, 0xE5DC74BC51848A51ULL }, { 8, 0ULL, 0x24CCC9ACAA9F65E4ULL },\n { 9, 0ULL, 0x14D5001C15DD3F2BULL }, { 16, 0ULL, 0x981B17D36C7498C9ULL },\n { 17, 0ULL, 0x796F5ACD3A60F862ULL }, { 64, 0ULL, 0x9CB48487720EC49DULL },\n { 128, 0ULL, 0xFCFF24126754D861ULL }, { 129, 0ULL, 0x98F1B0A679A2CA29ULL },\n { 222, 0ULL, 0xB9163B558664D356ULL }, { 240, 0ULL, 0x81C3C2B67F568CCFULL },\n { 241, 0ULL, 0xC5A639ECD2030E5EULL }, { 403, 0ULL, 0xCDEB804D65C6DEA4ULL },\n { 512, 0ULL, 0x617E49599013CB6BULL }, { 2048, 0ULL, 0xDD59E2C3A5F038E0ULL },\n { 4160, 0ULL, 0x4F323B15321E94E1ULL },\n};\n\nconstexpr Case64 kXXH3Seeded[] = {\n { 0, 0x9E3779B185EBCA8DULL, 0xA8A6B918B2F0364AULL },\n { 1, 0x9E3779B185EBCA8DULL, 0x032BE332DD766EF8ULL },\n { 16, 0x9E3779B185EBCA8DULL, 0x663F29333B4DB6B1ULL },\n { 129, 0x9E3779B185EBCA8DULL, 0x21FFFDBCA099C844ULL },\n { 241, 0x9E3779B185EBCA8DULL, 0xDDA9B0A161D4829AULL },\n { 4160, 0x9E3779B185EBCA8DULL, 0x1BF6F5FAF9EECABDULL },\n};\n\n// XSUM_XXH128_testdata, seed 0.\nconstexpr Case128 kXXH128[] = {\n { 0, 0x6001C324468D497FULL, 0x99AA06D3014798D8ULL },\n { 1, 0xC44BDFF4074EECDBULL, 0xA6CD5E9392000F6AULL },\n { 16, 0x562980258A998629ULL, 0xC68C368ECF8A9C05ULL },\n { 129, 0x86C9E3BC8F0A3B5CULL, 0x03815FC91F1B30B6ULL },\n { 241, 0xC5A639ECD2030E5EULL, 0x99A80ECF0ECFC647ULL },\n { 4160, 0x4F323B15321E94E1ULL, 0x67140711C1E3E335ULL },\n};\n\n} // namespace\n\nint main() {\n for (const auto& c : kXXH32) {\n const auto buf = make_sanity_buffer(c.len);\n assert(XXH32(buf.data(), c.len, c.seed) == c.expected);\n }\n for (const auto& c : kXXH64) {\n const auto buf = make_sanity_buffer(c.len);\n assert(XXH64(buf.data(), c.len, c.seed) == c.expected);\n }\n for (const auto& c : kXXH3) {\n const auto buf = make_sanity_buffer(c.len);\n assert(XXH3_64bits(buf.data(), c.len) == c.expected);\n }\n for (const auto& c : kXXH3Seeded) {\n const auto buf = make_sanity_buffer(c.len);\n assert(XXH3_64bits_withSeed(buf.data(), c.len, c.seed) == c.expected);\n }\n for (const auto& c : kXXH128) {\n const auto buf = make_sanity_buffer(c.len);\n const XXH128_hash_t h = XXH3_128bits(buf.data(), c.len);\n assert(h.low64 == c.lo && h.high64 == c.hi);\n }\n\n // --- streaming must agree with the one-shot form ---\n // Split at 3 bytes, which is neither the 16-byte stripe nor the 256-byte\n // block, so the internal buffering path is actually exercised.\n {\n const auto buf = make_sanity_buffer(4160);\n XXH64_state_t* st = XXH64_createState();\n assert(st != nullptr);\n assert(XXH64_reset(st, 0) == XXH_OK);\n assert(XXH64_update(st, buf.data(), 3) == XXH_OK);\n assert(XXH64_update(st, buf.data() + 3, buf.size() - 3) == XXH_OK);\n assert(XXH64_digest(st) == 0xEEE6A4E2AC952A5EULL);\n assert(XXH64_freeState(st) == XXH_OK);\n\n XXH3_state_t* s3 = XXH3_createState();\n assert(s3 != nullptr);\n assert(XXH3_64bits_reset(s3) == XXH_OK);\n assert(XXH3_64bits_update(s3, buf.data(), 3) == XXH_OK);\n assert(XXH3_64bits_update(s3, buf.data() + 3, buf.size() - 3) == XXH_OK);\n assert(XXH3_64bits_digest(s3) == 0x4F323B15321E94E1ULL);\n assert(XXH3_freeState(s3) == XXH_OK);\n }\n\n // --- the canonical (big-endian) serialization round-trips ---\n {\n const auto buf = make_sanity_buffer(512);\n const std::uint64_t h64 = XXH64(buf.data(), buf.size(), 0);\n XXH64_canonical_t c64;\n XXH64_canonicalFromHash(&c64, h64);\n assert(c64.digest[0] == static_cast(h64 >> 56)); // big-endian, always\n assert(XXH64_hashFromCanonical(&c64) == h64);\n\n const XXH128_hash_t h128 = XXH3_128bits(buf.data(), buf.size());\n XXH128_canonical_t c128;\n XXH128_canonicalFromHash(&c128, h128);\n const XXH128_hash_t back = XXH128_hashFromCanonical(&c128);\n assert(back.low64 == h128.low64 && back.high64 == h128.high64);\n }\n\n // --- a single flipped bit must change the digest ---\n {\n auto buf = make_sanity_buffer(4160);\n const std::uint64_t before = XXH3_64bits(buf.data(), buf.size());\n buf[buf.size() / 2] ^= 0x01;\n assert(XXH3_64bits(buf.data(), buf.size()) != before);\n }\n\n return 0;\n}\n" + } + ] + }, + { + "project": "yaml-cpp-v080", + "text": "# yaml-cpp 0.8.0 test project: the same assertions as tests/examples/yaml-cpp\n# (0.9.0) minus the float formatting 0.9.0 added, plus a compile-time probe\n# that 0.9.0's new header is absent, so a member silently resolving the newer\n# version fails instead of passing.\n# `compat` redirect inherited from the workspace root.\n[package]\nname = \"yaml-cpp-v080-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nyaml-cpp = \"0.8.0\"\n", + "files": [ + { + "path": "tests/examples/yaml-cpp-v080/tests/yaml.cpp", + "text": "// Behavioral test for compat.yaml-cpp 0.8.0 -- the same checks as\n// tests/examples/yaml-cpp (0.9.0) minus the float formatting 0.9.0 added.\n//\n// Each check reaches symbols defined in the package's own .cpp files, so a\n// package that compiled nothing cannot pass by being header-only by accident:\n//\n// YAML::Load, node access, conversions -> parse.cpp, node_data.cpp, convert.cpp\n// anchors and aliases -> nodebuilder.cpp, singledocparser.cpp\n// YAML::LoadAll -> parse.cpp, parser.cpp\n// YAML::Emitter, YAML::Dump -> emitter.cpp, emitterstate.cpp, emit.cpp\n// malformed input -> scanner.cpp, exceptions.cpp\n// YAML::BuildGraphOfNextDocument -> contrib/graphbuilder*.cpp\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n\n#include \n#include \n#include \n#include \n\n// The package builds objects, not a shared library, so every yaml-cpp\n// declaration this TU sees must be plain -- not dllimport. The package\n// delivers that with a shim in front of yaml-cpp/dll.h, because no descriptor\n// key carries a define to a consumer's TUs. On Linux the difference is only a\n// visibility attribute and everything links anyway; on the MSVC ABI it is a\n// link failure. Asserting it at compile time fails everywhere the moment the\n// shim stops being reached.\n#ifndef YAML_CPP_STATIC_DEFINE\n# error \"YAML_CPP_STATIC_DEFINE did not reach the consumer: the yaml-cpp/dll.h shim was not found first\"\n#endif\n\n// Proves this member resolved 0.8.0 and not the newest version: the header\n// 0.9.0 added must not exist here.\n#if __has_include()\n# error \"yaml-cpp/fptostring.h is present: this member did not resolve compat.yaml-cpp 0.8.0\"\n#endif\n\nnamespace {\n\nconst char *const kDoc = R\"(\nname: mcpp-index\nversion: 3\nratio: 0.25\nenabled: true\ntags: [yaml, cpp, \"quoted, with comma\"]\nbase: &base\n host: localhost\n port: 8080\nderived:\n <<: *base\n alias_of_base: *base\ntext: |\n line one\n line two\n)\";\n\nbool loads_and_converts() {\n YAML::Node root = YAML::Load(kDoc);\n if (!root.IsMap()) return false;\n if (root[\"name\"].as() != \"mcpp-index\") return false;\n if (root[\"version\"].as() != 3) return false;\n if (root[\"ratio\"].as() != 0.25) return false;\n if (!root[\"enabled\"].as()) return false;\n\n const YAML::Node tags = root[\"tags\"];\n if (!tags.IsSequence() || tags.size() != 3) return false;\n if (tags[2].as() != \"quoted, with comma\") return false;\n if (tags.as>().front() != \"yaml\") return false;\n\n if (root[\"text\"].as() != \"line one\\nline two\\n\") return false;\n\n // A missing key is reported as undefined and a fallback applies; an\n // explicit conversion of the wrong kind throws.\n if (root[\"absent\"].IsDefined()) return false;\n if (root[\"absent\"].as(42) != 42) return false;\n try {\n (void)root[\"name\"].as();\n return false;\n } catch (const YAML::BadConversion &) {\n }\n return true;\n}\n\nbool resolves_aliases() {\n YAML::Node root = YAML::Load(kDoc);\n const YAML::Node alias = root[\"derived\"][\"alias_of_base\"];\n if (alias[\"port\"].as() != 8080) return false;\n // An alias is the SAME node as its anchor, not a copy of it.\n return alias.is(root[\"base\"]);\n}\n\nbool loads_every_document() {\n const std::vector docs = YAML::LoadAll(\"a: 1\\n---\\nb: 2\\n---\\n- 3\\n\");\n return docs.size() == 3 && docs[0][\"a\"].as() == 1\n && docs[1][\"b\"].as() == 2 && docs[2][0].as() == 3;\n}\n\nbool emits_and_round_trips() {\n YAML::Emitter out;\n out << YAML::BeginMap;\n out << YAML::Key << \"list\" << YAML::Value << YAML::Flow\n << YAML::BeginSeq << 1 << 2 << 3 << YAML::EndSeq;\n out << YAML::Key << \"nested\" << YAML::Value << YAML::BeginMap\n << YAML::Key << \"k\" << YAML::Value << \"v\" << YAML::EndMap;\n out << YAML::EndMap;\n if (!out.good()) return false;\n if (std::string(out.c_str()) != \"list: [1, 2, 3]\\nnested:\\n k: v\") return false;\n\n YAML::Node again = YAML::Load(out.c_str());\n if (again[\"list\"][2].as() != 3) return false;\n\n // A node built in code and dumped comes back with the same content.\n YAML::Node built;\n built[\"x\"] = 7;\n built[\"y\"].push_back(\"first\");\n built[\"y\"].push_back(\"second\");\n YAML::Node back = YAML::Load(YAML::Dump(built));\n return back[\"x\"].as() == 7 && back[\"y\"].size() == 2\n && back[\"y\"][1].as() == \"second\";\n}\n\nbool rejects_malformed_input() {\n try {\n (void)YAML::Load(\"key: [unterminated\\nother: 1\\n\");\n } catch (const YAML::ParserException &e) {\n // The mark points into the document rather than being left unset.\n return e.mark.line >= 0 && !std::string(e.what()).empty();\n }\n return false;\n}\n\n} // namespace\n\n// Upstream declares GraphBuilderInterface's destructor pure virtual and\n// defines it nowhere (0.8.0 and 0.9.0 alike), so every class derived from it\n// -- upstream's own GraphBuilder included -- needs the definition from\n// its user. This is that definition, as any consumer of the contrib API must\n// write it. BuildGraphOfNextDocument below still comes only from\n// src/contrib/graphbuilder.cpp, so the check still proves contrib was built.\nYAML::GraphBuilderInterface::~GraphBuilderInterface() = default;\n\nnamespace {\n\n// Counts what the contrib GraphBuilder is told, which only the two\n// src/contrib/*.cpp translation units implement.\nstruct Counter : YAML::GraphBuilderInterface {\n int scalars = 0, sequences = 0, maps = 0;\n void *NewNull(const YAML::Mark &, void *) override { return this; }\n void *NewScalar(const YAML::Mark &, const std::string &, void *,\n const std::string &) override {\n ++scalars;\n return this;\n }\n void *NewSequence(const YAML::Mark &, const std::string &, void *) override {\n ++sequences;\n return this;\n }\n void AppendToSequence(void *, void *) override {}\n void SequenceComplete(void *) override {}\n void *NewMap(const YAML::Mark &, const std::string &, void *) override {\n ++maps;\n return this;\n }\n void AssignInMap(void *, void *, void *) override {}\n void MapComplete(void *) override {}\n void *AnchorReference(const YAML::Mark &, void *node) override { return node; }\n};\n\nbool builds_a_graph() {\n std::istringstream in(\"a: [1, 2]\\nb: {c: 3}\\n\");\n YAML::Parser parser(in);\n Counter counter;\n if (YAML::BuildGraphOfNextDocument(parser, counter) == nullptr) return false;\n // keys a, b, c and values 1, 2, 3\n return counter.scalars == 6 && counter.sequences == 1 && counter.maps == 2;\n}\n\n} // namespace\n\nint main() {\n struct Check {\n const char *name;\n bool (*run)();\n } checks[] = {\n {\"loads_and_converts\", loads_and_converts},\n {\"resolves_aliases\", resolves_aliases},\n {\"loads_every_document\", loads_every_document},\n {\"emits_and_round_trips\", emits_and_round_trips},\n {\"rejects_malformed_input\", rejects_malformed_input},\n {\"builds_a_graph\", builds_a_graph},\n };\n bool ok = true;\n for (const Check &c : checks) {\n const bool passed = c.run();\n std::printf(\"%s %s\\n\", passed ? \"ok \" : \"FAIL\", c.name);\n ok = ok && passed;\n }\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "yaml-cpp", + "text": "# yaml-cpp test project: consumes compat.yaml-cpp 0.9.0 and asserts behavior\n# under `mcpp test` -- parsing, aliases, multi-document loads, emitting, parse\n# errors, the contrib GraphBuilder and 0.9.0's shortest float formatting.\n# tests/examples/yaml-cpp-v080 covers 0.8.0. Also measured on openkal\n# (tests/openkal/members.toml).\n# `compat` redirect inherited from the workspace root.\n[package]\nname = \"yaml-cpp-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nyaml-cpp = \"0.9.0\"\n", + "files": [ + { + "path": "tests/examples/yaml-cpp/tests/yaml.cpp", + "text": "// Behavioral test for compat.yaml-cpp 0.9.0.\n//\n// Each check reaches symbols defined in the package's own .cpp files, so a\n// package that compiled nothing cannot pass by being header-only by accident:\n//\n// YAML::Load, node access, conversions -> parse.cpp, node_data.cpp, convert.cpp\n// anchors and aliases -> nodebuilder.cpp, singledocparser.cpp\n// YAML::LoadAll -> parse.cpp, parser.cpp\n// YAML::Emitter, YAML::Dump -> emitter.cpp, emitterstate.cpp, emit.cpp\n// malformed input -> scanner.cpp, exceptions.cpp\n// YAML::BuildGraphOfNextDocument -> contrib/graphbuilder*.cpp\n// YAML::FpToString -> fptostring.cpp (new in 0.9.0)\n//\n// Returns non-zero on any mismatch.\n#include \n#include \n#include \n\n#include \n#include \n#include \n#include \n\n// The package builds objects, not a shared library, so every yaml-cpp\n// declaration this TU sees must be plain -- not dllimport. The package\n// delivers that with a shim in front of yaml-cpp/dll.h, because no descriptor\n// key carries a define to a consumer's TUs. On Linux the difference is only a\n// visibility attribute and everything links anyway; on the MSVC ABI it is a\n// link failure. Asserting it at compile time fails everywhere the moment the\n// shim stops being reached.\n#ifndef YAML_CPP_STATIC_DEFINE\n# error \"YAML_CPP_STATIC_DEFINE did not reach the consumer: the yaml-cpp/dll.h shim was not found first\"\n#endif\n\nnamespace {\n\nconst char *const kDoc = R\"(\nname: mcpp-index\nversion: 3\nratio: 0.25\nenabled: true\ntags: [yaml, cpp, \"quoted, with comma\"]\nbase: &base\n host: localhost\n port: 8080\nderived:\n <<: *base\n alias_of_base: *base\ntext: |\n line one\n line two\n)\";\n\nbool loads_and_converts() {\n YAML::Node root = YAML::Load(kDoc);\n if (!root.IsMap()) return false;\n if (root[\"name\"].as() != \"mcpp-index\") return false;\n if (root[\"version\"].as() != 3) return false;\n if (root[\"ratio\"].as() != 0.25) return false;\n if (!root[\"enabled\"].as()) return false;\n\n const YAML::Node tags = root[\"tags\"];\n if (!tags.IsSequence() || tags.size() != 3) return false;\n if (tags[2].as() != \"quoted, with comma\") return false;\n if (tags.as>().front() != \"yaml\") return false;\n\n if (root[\"text\"].as() != \"line one\\nline two\\n\") return false;\n\n // A missing key is reported as undefined and a fallback applies; an\n // explicit conversion of the wrong kind throws.\n if (root[\"absent\"].IsDefined()) return false;\n if (root[\"absent\"].as(42) != 42) return false;\n try {\n (void)root[\"name\"].as();\n return false;\n } catch (const YAML::BadConversion &) {\n }\n return true;\n}\n\nbool resolves_aliases() {\n YAML::Node root = YAML::Load(kDoc);\n const YAML::Node alias = root[\"derived\"][\"alias_of_base\"];\n if (alias[\"port\"].as() != 8080) return false;\n // An alias is the SAME node as its anchor, not a copy of it.\n return alias.is(root[\"base\"]);\n}\n\nbool loads_every_document() {\n const std::vector docs = YAML::LoadAll(\"a: 1\\n---\\nb: 2\\n---\\n- 3\\n\");\n return docs.size() == 3 && docs[0][\"a\"].as() == 1\n && docs[1][\"b\"].as() == 2 && docs[2][0].as() == 3;\n}\n\nbool emits_and_round_trips() {\n YAML::Emitter out;\n out << YAML::BeginMap;\n out << YAML::Key << \"list\" << YAML::Value << YAML::Flow\n << YAML::BeginSeq << 1 << 2 << 3 << YAML::EndSeq;\n out << YAML::Key << \"nested\" << YAML::Value << YAML::BeginMap\n << YAML::Key << \"k\" << YAML::Value << \"v\" << YAML::EndMap;\n out << YAML::EndMap;\n if (!out.good()) return false;\n if (std::string(out.c_str()) != \"list: [1, 2, 3]\\nnested:\\n k: v\") return false;\n\n YAML::Node again = YAML::Load(out.c_str());\n if (again[\"list\"][2].as() != 3) return false;\n\n // A node built in code and dumped comes back with the same content.\n YAML::Node built;\n built[\"x\"] = 7;\n built[\"y\"].push_back(\"first\");\n built[\"y\"].push_back(\"second\");\n YAML::Node back = YAML::Load(YAML::Dump(built));\n return back[\"x\"].as() == 7 && back[\"y\"].size() == 2\n && back[\"y\"][1].as() == \"second\";\n}\n\nbool rejects_malformed_input() {\n try {\n (void)YAML::Load(\"key: [unterminated\\nother: 1\\n\");\n } catch (const YAML::ParserException &e) {\n // The mark points into the document rather than being left unset.\n return e.mark.line >= 0 && !std::string(e.what()).empty();\n }\n return false;\n}\n\n} // namespace\n\n// Upstream declares GraphBuilderInterface's destructor pure virtual and\n// defines it nowhere (0.8.0 and 0.9.0 alike), so every class derived from it\n// -- upstream's own GraphBuilder included -- needs the definition from\n// its user. This is that definition, as any consumer of the contrib API must\n// write it. BuildGraphOfNextDocument below still comes only from\n// src/contrib/graphbuilder.cpp, so the check still proves contrib was built.\nYAML::GraphBuilderInterface::~GraphBuilderInterface() = default;\n\nnamespace {\n\n// Counts what the contrib GraphBuilder is told, which only the two\n// src/contrib/*.cpp translation units implement.\nstruct Counter : YAML::GraphBuilderInterface {\n int scalars = 0, sequences = 0, maps = 0;\n void *NewNull(const YAML::Mark &, void *) override { return this; }\n void *NewScalar(const YAML::Mark &, const std::string &, void *,\n const std::string &) override {\n ++scalars;\n return this;\n }\n void *NewSequence(const YAML::Mark &, const std::string &, void *) override {\n ++sequences;\n return this;\n }\n void AppendToSequence(void *, void *) override {}\n void SequenceComplete(void *) override {}\n void *NewMap(const YAML::Mark &, const std::string &, void *) override {\n ++maps;\n return this;\n }\n void AssignInMap(void *, void *, void *) override {}\n void MapComplete(void *) override {}\n void *AnchorReference(const YAML::Mark &, void *node) override { return node; }\n};\n\nbool builds_a_graph() {\n std::istringstream in(\"a: [1, 2]\\nb: {c: 3}\\n\");\n YAML::Parser parser(in);\n Counter counter;\n if (YAML::BuildGraphOfNextDocument(parser, counter) == nullptr) return false;\n // keys a, b, c and values 1, 2, 3\n return counter.scalars == 6 && counter.sequences == 1 && counter.maps == 2;\n}\n\n// 0.9.0 formats floating point by the shortest representation that reads\n// back to the same value; 0.8.0 printed max_digits10 digits\n// (\"0.10000000000000001\"). The emitter uses the same routine.\nbool formats_floats_shortest() {\n if (YAML::FpToString(0.1) != \"0.1\") return false;\n if (YAML::FpToString(1.5f) != \"1.5\") return false;\n YAML::Emitter out;\n out << 0.1;\n return std::string(out.c_str()) == \"0.1\";\n}\n\n} // namespace\n\nint main() {\n struct Check {\n const char *name;\n bool (*run)();\n } checks[] = {\n {\"loads_and_converts\", loads_and_converts},\n {\"resolves_aliases\", resolves_aliases},\n {\"loads_every_document\", loads_every_document},\n {\"emits_and_round_trips\", emits_and_round_trips},\n {\"rejects_malformed_input\", rejects_malformed_input},\n {\"builds_a_graph\", builds_a_graph},\n {\"formats_floats_shortest\", formats_floats_shortest},\n };\n bool ok = true;\n for (const Check &c : checks) {\n const bool passed = c.run();\n std::printf(\"%s %s\\n\", passed ? \"ok \" : \"FAIL\", c.name);\n ok = ok && passed;\n }\n return ok ? 0 : 1;\n}\n" + } + ] + }, + { + "project": "yyjson", + "text": "[package]\nname = \"yyjson-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nyyjson = \"0.12.0\"\n", + "files": [ + { + "path": "tests/examples/yyjson/tests/parse.cpp", + "text": "// Smoke test — verify compat.yyjson builds, links, and works\n#include \"yyjson.h\"\nimport std;\n\nint main() {\n const char* json = R\"({\"name\":\"mcpp\",\"version\":\"0.12.0\"})\";\n yyjson_doc* doc = yyjson_read(json, strlen(json), 0);\n if (!doc) return 1;\n auto* root = yyjson_doc_get_root(doc);\n auto* name = yyjson_obj_get(root, \"name\");\n if (!name || std::string_view{yyjson_get_str(name)} != \"mcpp\") return 2;\n std::println(\"compat.yyjson smoke test: ok\");\n yyjson_doc_free(doc);\n return 0;\n}\n" + } + ] + }, + { + "project": "zlib", + "text": "# compat.zlib test project. It asserts behaviour (a deflate round trip and a gz\n# file written and read back) and the one agreement a configuration that differs\n# per target can break silently: the width of z_off_t as the compiled library\n# reports it and as this consumer computes it from zconf.h.\n# `compat` redirect inherited from the workspace root.\n[package]\nname = \"zlib-tests\"\nversion = \"0.1.0\"\n\n[dependencies.compat]\nzlib = \"1.3.2\"\n", + "files": [ + { + "path": "tests/examples/zlib/tests/zlib.cpp", + "text": "// compat.zlib: behaviour, and agreement between the library and its consumer.\n#include \n\n#include \n#include \n#include \n\nnamespace {\n\nint failures = 0;\n\nvoid check(bool held, const char* what) {\n if (!held) { std::printf(\"FAIL: %s\\n\", what); ++failures; }\n}\n\n// zlibCompileFlags() bits 6 and 7 state the size of z_off_t the library was\n// compiled with: 0 is 2 bytes, 1 is 4, 2 is 8, 3 is another size.\nunsigned long library_off_t_size() {\n switch ((zlibCompileFlags() >> 6) & 3u) {\n case 0: return 2;\n case 1: return 4;\n case 2: return 8;\n default: return 0;\n }\n}\n\n} // namespace\n\nint main() {\n check(library_off_t_size() == sizeof(z_off_t),\n \"the library and this consumer agree on the width of z_off_t\");\n\n const std::string text(4096, 'z');\n uLongf bound = compressBound(static_cast(text.size()));\n std::string packed(bound, '\\0');\n check(compress(reinterpret_cast(packed.data()), &bound,\n reinterpret_cast(text.data()),\n static_cast(text.size())) == Z_OK,\n \"a buffer is compressed\");\n std::string unpacked(text.size(), '\\0');\n uLongf size = static_cast(unpacked.size());\n check(uncompress(reinterpret_cast(unpacked.data()), &size,\n reinterpret_cast(packed.data()), bound) == Z_OK\n && size == text.size() && unpacked == text,\n \"and decompresses to what it was\");\n\n const char* name = \"zlib-test.gz\";\n gzFile out = gzopen(name, \"wb\");\n check(out != nullptr, \"a gz file is opened for writing\");\n if (out) {\n check(gzwrite(out, text.data(), static_cast(text.size()))\n == static_cast(text.size()), \"the gz file is written\");\n gzclose(out);\n }\n gzFile in = gzopen(name, \"rb\");\n check(in != nullptr, \"the gz file is opened for reading\");\n if (in) {\n std::string back(text.size(), '\\0');\n check(gzread(in, back.data(), static_cast(back.size()))\n == static_cast(text.size()) && back == text,\n \"and reads back what was written\");\n check(gzseek(in, 10, SEEK_SET) == 10 && gztell(in) == 10,\n \"a gz file seeks and reports its offset through z_off_t\");\n gzclose(in);\n }\n std::remove(name);\n\n if (failures == 0) std::printf(\"compat.zlib: every observation held\\n\");\n return failures == 0 ? 0 : 1;\n}\n" + } + ] + } + ] +} diff --git a/data/toml-schema.json b/data/toml-schema.json index c5fe572..5c89ce8 100644 --- a/data/toml-schema.json +++ b/data/toml-schema.json @@ -1,6 +1,6 @@ { - "sourceVersion": "2026.10.11.1", - "sourceCommit": "3fb2d0cc89296c527fcda710792b67bb7a481bed", + "sourceVersion": "2026.10.17.1", + "sourceCommit": "684a306bced037014da08b038412f57c203d0bc9", "sections": [ { "header": "[build]", @@ -715,6 +715,11 @@ ], "since": "2026.9.12.2" }, + { + "key": "windows_long_paths", + "type": "string", + "unmodelled": true + }, { "key": "windows_subsystem", "type": "enum", diff --git a/docs/libraries.md b/docs/libraries.md new file mode 100644 index 0000000..ab79979 --- /dev/null +++ b/docs/libraries.md @@ -0,0 +1,88 @@ +# Libraries + +Open **MC++ → Libraries**, search for a package, and click it to open its detail page. +The default catalog is the official mcpp-index already installed on this machine. +The footer distinguishes rereading local files from updating the index with mcpp. + +The detail header shows usage, C++ standard and license tags. Choose the target +project in the upper right, then select a version available for the current +environment. This platform describes the extension host, not a cross-compilation target. + +Use the arrow beside the add button to choose the dependency type: + +| Type | Behavior | +| --- | --- | +| Project dependency | Runs `mcpp add @` | +| Development dependency | Runs `mcpp add @ --dev` | +| Build dependency | Shows a copyable `[build-dependencies]` entry and opens `mcpp.toml` for editing | + +Current mcpp does not provide a flag for adding build dependencies. Moving an +existing declaration between project and development groups also opens the +manifest with an appropriate snippet; the extension does not silently duplicate +or migrate it. Additions use exact versions. Existing declarations are preserved +by default, and lockfile resolutions are shown separately from direct declarations. + +Usage shows the index's real test examples, preferring the package's own named +test project. Local checkout tests take priority. Official packaged indexes often +omit tests, so the extension includes a pinned upstream test snapshot for offline +use, with its commit shown on the page. Open the complete source in a read-only +editor; this also applies to local checkout sources. Copy and source actions sit +beside the example heading. Custom indexes only use their own tests. Packages without matching tests +keep an explicit empty state. Platform versions, build information and source +diagnostics can be expanded without leaving the page. + +In **Project → Dependencies**, use the graph icon to open a Mermaid view, +initially expanded to three dependency levels. The palette command is **mcpp: Show Project +Dependency Graph**. Relations come from project/local-path manifests and local +index descriptors; lockfiles supply versions rather than parent edges. This is a +declaration graph, so conditional or descriptor-level relations may differ from a +resolved build. Missing local metadata is marked with a dashed node. The page +supports zoom, refresh, opening the manifest and copying Mermaid source. The +package provider retains its 120-node/240-edge data limit; the shared canvas also +bounds every layout before rendering. + +The dependency list shows direct `namespace.package@version` declarations and +distinguishes index, path and git sources. Click a row to open the graph focused +on that dependency. Graph edges keep the full package coordinate together; +colored nodes show minimal tags and dependency counts. The primary canvas omits +absolute paths and diagnostic paragraphs; the legend and source are folded. + +**Search other registries** is optional and off by default. Results from these +registries appear in a separate group and open limited previews. They cannot be +added directly without a complete local descriptor. + +If the official catalog is missing, update the mcpp index or configure +`mcpp.library.indexPath` to an index directory containing `pkgs/`. A custom path +is identified as a custom source; global index updates do not update an arbitrary +checkout. The legacy official path is supported, while the xlings tool catalog +is not automatically substituted for mcpp-index. + +The dependency graph supports cursor-centered wheel zoom and click-and-drag panning. Use Fit to restore the complete graph; keyboard `+`, `-`, and `0` provide zoom and reset. + +The project sidebar keeps **C++ Modules Deps** inside **Basics**, followed by a +separate, initially collapsed **Dependencies** section containing direct packages. +The graph panel switches between **Packages** and **C++ Modules**, preserving each +view's selection and viewport. + +Module relationships come from the installed C++ Modules language server's +`mcppls query module --graph --format json` output. No C++ source parser is added. +Internal relationships can be explored at any depth; external modules, including +local path packages, stop after one outgoing hop from the project. Entry cards +identify explicit target main files, `[lib].path`, or existing default entry files. +Click a module to open its provider source read-only beside the graph. + +The initial depth is 3; choose 5 or All, or activate a card's `+N` control to +expand one branch. Collapse keeps shared nodes reached by other branches. Search +also finds hidden nodes and opens a focused neighborhood with direct importers; +Back to project restores the overview. Each Mermaid layout is limited to 150 +nodes and 300 edges, computed before rendering. All respects these budgets. +The mcppls CLI currently limits its full graph response to 500 module records; +truncated or still-indexing results carry a compact incomplete-data notice. +Use Refresh after source edits or indexing completes. Precise declaration lines +and per-import re-export flags are not supplied by this graph endpoint, so this +view opens the source file and does not invent those details. + +Package details use the available editor width, including fullscreen and ultrawide +windows. Side gutters stay within 16–40px; the metadata column stays within +240–320px and additional width goes to the example code. Below 900px the page +uses a single column for split editors and narrow windows. diff --git a/docs/settings.md b/docs/settings.md index 2443de8..a343afe 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -738,9 +738,9 @@ Age buckets used to group cache entries in the cache view, written as "1d", "7d" ### `mcpp.library.indexPath` — advanced -Leave it empty to use the indexes under the mcpp home. The home is `$MCPP_HOME`, then `~/.mcpp`; when neither holds an index, the extension asks `mcpp self env --format json` where it is. +Leave empty to browse the official mcpp-index at /indices/mcpplibs (legacy registry/data/mcpplibs is supported). MCPP_HOME takes precedence; otherwise the extension asks mcpp self env. Set a directory containing pkgs/ or a collection of indexes to browse a custom source. -留空则使用 mcpp 主目录下的索引。主目录依次取 `$MCPP_HOME`、`~/.mcpp`;两者都没有索引时,扩展会用 `mcpp self env --format json` 询问 mcpp 主目录在哪。 +留空则读取 mcpp 主目录下 indices/mcpplibs 中的官方 mcpp-index,兼容旧位置 registry/data/mcpplibs。优先使用 MCPP_HOME,否则通过 mcpp self env 获取主目录。可指定带 pkgs/ 的目录或索引集合目录来浏览自定义来源。 | | | |---|---| @@ -752,9 +752,9 @@ Leave it empty to use the indexes under the mcpp home. The home is `$MCPP_HOME`, ### `mcpp.library.networkSearch` -The local index already lists every published package, so this is off by default. Turning it on also searches the other registries with `mcpp search`, which may use the network and can only be parsed on a best-effort basis. +Off by default. Also searches other registries with mcpp search; remote results have limited previews and cannot be added directly. -本地索引已经列出所有已发布的包,所以默认关闭。打开后会额外用 `mcpp search` 搜索其它 registry,可能联网,且只能尽力解析。 +默认关闭。开启后额外用 mcpp search 搜索其它 registry;搜索结果仅提供有限预览,不能直接添加。 | | | |---|---| diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json index a326dd4..020afb3 100644 --- a/l10n/bundle.l10n.zh-cn.json +++ b/l10n/bundle.l10n.zh-cn.json @@ -342,7 +342,7 @@ "Toolchain": "工具链", "Toolchain managed by mcpp": "mcpp 管理的工具链", "Toolchain {0} installed. Choose a global default from the latest list?": "工具链 {0} 安装完成。是否从最新列表中选择全局默认?", - "Unavailable": "不可用", + "Unavailable": "暂无元数据", "Unexpected error: {0}": "发生未预期错误:{0}", "Unknown": "未知", "Use": "用法", @@ -471,5 +471,122 @@ "Only C++ Modules is serving C++": "仅 C++ Modules 在提供 C++ 语言服务", "No other C++ language service (C/C++, clangd, ccls) is active, so no result is shown twice.": "没有其他 C++ 语言服务(C/C++、clangd、ccls)在工作,结果不会重复出现。", "Other C++ language services": "其他 C++ 语言服务", - "They answer the same C++ files, so results appear twice. Click to turn their C++ language features off; C++ Modules asks whether for this workspace or everywhere.": "它们也在处理同一批 C++ 文件,结果会出现两次。点击即可关闭它们的 C++ 语言功能;C++ Modules 会询问只对本工作区还是对所有地方生效。" + "They answer the same C++ files, so results appear twice. Click to turn their C++ language features off; C++ Modules asks whether for this workspace or everywhere.": "它们也在处理同一批 C++ 文件,结果会出现两次。点击即可关闭它们的 C++ 语言功能;C++ Modules 会询问只对本工作区还是对所有地方生效。", + "Libraries": "库", + "Source details": "来源详情", + "Other registries": "其它 registry", + "Set index path": "设置索引路径", + "Update mcpp index": "更新 mcpp 索引", + "Show output": "查看输出", + "Clear search": "清空搜索", + "Search other registries": "搜索其它 registry", + "Declared": "已声明", + "Reread locally": "重新读取本地索引", + "Could not read the index: {0}": "无法读取索引:{0}", + "Searching other registries…": "正在搜索其它 registry…", + "Other registry": "其它 registry", + "Official index": "官方索引", + "Custom index": "自定义索引", + "Browse libraries; select an mcpp project in the detail page to add dependencies.": "可以浏览库;请在详情页选择 mcpp 项目后添加依赖。", + "No local mcpp-index found": "未找到本地 mcpp-index", + "{0} · {1} packages · local": "{0} · {1} 个包 · 本地", + "Target project": "目标项目", + "Choose project": "选择项目", + "Environment platform": "当前环境平台", + "Version": "版本", + "Other platforms (read only)": "其它平台(只读)", + "Resolved in lockfile": "锁定解析", + "Source dependency": "源码依赖", + "No verified import or header path is available locally. See the repository or index site for usage.": "本地没有可验证的模块名或头文件路径,请查看仓库或索引站的用法说明。", + "From the index's example source": "来自索引示例源码", + "Source and diagnostics": "来源与诊断", + "Reading package details…": "正在读取包详情…", + "Adding dependency…": "正在添加依赖…", + "Changing dependency groups is not supported here. Edit mcpp.toml to move the declaration.": "此处暂不支持迁移依赖分组,请编辑 mcpp.toml 移动声明。", + "Retry reading details": "重新读取详情", + "Open mcpp.toml": "打开 mcpp.toml", + "Open source file": "打开源文件", + "Search preview: only the fields returned by mcpp search are available.": "搜索预览:仅展示 mcpp search 返回的字段。", + "Copied": "已复制", + "Copy failed": "复制失败", + "Opened in your browser.": "已在浏览器中打开。", + "Could not open the browser. Link copied to clipboard.": "无法打开浏览器,链接已复制到剪贴板。", + "Could not open the browser or copy the link: {0}": "无法打开浏览器或复制链接:{0}", + "Could not add the dependency. See output for details.": "无法添加依赖,请查看输出了解详情。", + "Could not read package details. See output for details.": "无法读取包详情,请查看输出了解详情。", + "This is a search preview. Set an index path containing this package to read its complete details.": "这是搜索预览。将索引路径设置为包含此包的目录后,可读取完整详情。", + "Search previews cannot be added. Open a complete local descriptor first.": "搜索预览无法直接添加,请先打开完整的本地包描述。", + "The package descriptor is missing. Reread the local index.": "包描述不存在,请重新读取本地索引。", + "Trust this workspace to run mcpp and add dependencies.": "信任此工作区后,才能运行 mcpp 和添加依赖。", + "Select an mcpp project before adding this library.": "请先选择 mcpp 项目,再添加此库。", + "This dependency name is ambiguous. Use a qualified package name in mcpp.toml.": "此依赖名称有歧义,请在 mcpp.toml 中使用完整包名。", + "This project uses a source dependency. Edit its manifest to change the source.": "项目已声明源码依赖,请编辑清单修改来源。", + "Version availability could not be confirmed. Retry reading this descriptor.": "暂时无法确认版本可用性,请重新读取包描述。", + "No version is published for the current environment platform.": "当前环境平台没有可用的发布版本。", + "Already declared": "已声明", + "Descriptor-level dependencies; the selected version may differ. Resolved values refer to this project's lockfile.": "这里展示包描述中的依赖,所选版本的依赖可能不同;解析版本来自当前项目的锁文件。", + "mcpp add failed (exit {0}). See output for details.": "mcpp add 失败(退出码 {0}),请查看输出了解详情。", + "Choose the target mcpp project": "选择目标 mcpp 项目", + "Dependencies will be added to this project's mcpp.toml": "依赖将添加到此项目的 mcpp.toml", + "Updating the global mcpp index; a configured custom checkout is only reread.": "正在更新全局 mcpp 索引;已配置的自定义检出目录仅会被重新读取。", + "The current environment platform is not recognized by this index.": "索引无法识别当前环境平台。", + "Versions are from descriptor text; mcpp will confirm availability when adding.": "版本来自描述符文本,添加时将由 mcpp 确认可用性。", + "Platforms and versions": "平台与版本", + "Add dependency": "添加依赖", + "Usage": "用法", + "Dependency type": "依赖类型", + "Project dependency": "项目依赖", + "Development dependency": "开发依赖", + "Build dependency": "构建依赖", + "Add development dependency": "添加开发依赖", + "Build dependencies are used by the build script. Copy this entry into mcpp.toml; mcpp add does not support this group yet.": "构建依赖供构建脚本使用;当前 mcpp add 尚不支持此分组,请复制此项并编辑 mcpp.toml。", + "Changing this declaration requires a manifest edit. Copy the entry and update its dependency group in mcpp.toml.": "请复制声明并在 mcpp.toml 中调整依赖分组;已有声明不会自动迁移。", + "The dependency declaration changed. Review mcpp.toml before adding it.": "依赖声明已变化,请先检查 mcpp.toml 再添加。", + "Updating mcpp index…": "正在更新 mcpp 索引…", + "Index update failed; showing the last available local snapshot.": "索引更新失败,继续显示上次可用的本地快照。", + "Index updated; {0} local packages.": "索引已更新,本地共 {0} 个包。", + "Bundled official index tests · commit {0}": "插件内置官方索引测试 · 提交 {0}", + "Project dependency graph": "项目依赖图", + "Copy Mermaid": "复制 Mermaid", + "Up to 3 dependency levels · {0} packages · {1} edges": "最多 3 层依赖 · {0} 个包 · {1} 条关系", + "Edges come from manifests and local index descriptors; lockfile versions are shown when available. This is a declaration graph, not a resolved build graph. Dashed nodes have no local dependency metadata; conditional edges may not apply to this environment.": "关系来自项目清单和本地索引描述符;有锁定版本时显示锁定版本。这是声明关系图,不代表实际构建解析结果。虚线节点缺少本地依赖元数据,条件关系可能不适用于当前环境。", + "Large graph truncated at 120 nodes or 240 edges.": "大型依赖图已截断(最多 120 个节点或 240 条关系)。", + "Fit": "适应窗口", + "Mermaid source": "Mermaid 源码", + "Could not render the graph. Copy Mermaid source or refresh to retry.": "依赖图渲染失败,可复制 Mermaid 源码或刷新重试。", + "Could not read project dependencies: {0}": "无法读取项目依赖:{0}", + "Open an mcpp project first.": "请先打开一个 mcpp 项目。", + "The project manifest is missing or unreadable.": "项目清单不存在或无法读取。", + "Reading project dependencies…": "正在读取项目依赖…", + "Source": "源码", + "Open read-only source": "打开只读源码", + "Dependency graph": "依赖图", + "Maximum dependency depth: 3": "最多展示 3 层依赖", + "Zoom out": "缩小", + "Zoom in": "放大", + "Legend and source": "图例与源码", + "Project": "项目", + "Declaration graph · dashed edges are conditional · lockfile supplies versions": "声明关系图 · 虚线为条件依赖 · 锁文件提供版本", + "index": "索引", + "build": "构建", + "conditional": "条件", + "Scroll to zoom · drag to pan": "滚轮缩放 · 按住拖动画布", + "C++ Modules Deps": "C++ Modules 依赖图", + "No module relationships were found for this project.": "此项目尚无模块关系。", + "View limit reached. Focus a node to explore its dependencies.": "已达到当前视图上限,可聚焦节点继续查看。", + "The source graph is incomplete. Refresh after indexing finishes.": "数据尚不完整,可在索引完成后刷新。", + "Expand next level": "展开下一层", + "Selected node": "已选节点", + "Graph view": "图视图", + "Packages": "包依赖", + "Depth": "深度", + "Find a node…": "查找节点…", + "Back to project": "返回项目", + "Select a node to explore its relationships.": "选择节点查看关联。", + "Collapse": "收起", + "Focus": "聚焦", + "Packages show declarations; modules show mcppls imports. External modules stop after one hop.": "包图显示声明关系;模块图来自 mcppls,外部模块只显示一层。", + "Source is unavailable for this module.": "此模块的源码不可用。", + "Choose a module source file": "选择模块源码文件", + "The source graph is incomplete. Some modules may be omitted.": "源图数据不完整,部分模块可能尚未包含。" } diff --git a/media/dependencyGraph.css b/media/dependencyGraph.css new file mode 100644 index 0000000..492c281 --- /dev/null +++ b/media/dependencyGraph.css @@ -0,0 +1,43 @@ +body { font-family: var(--vscode-font-family); color: var(--vscode-foreground); padding: 20px 24px; margin: 0; } +.graph-header,.graph-heading,nav,.zoom,.graph-legend { display:flex;align-items:center;gap:10px; } +.graph-header { justify-content:space-between; gap:16px; padding-bottom:16px; } +.graph-heading { min-width:0; flex-wrap:wrap; } h1 { font-size:17px;font-weight:600;margin:0; } +.project-name { color:var(--vscode-descriptionForeground); font-size:12px; } .depth-pill { font:11px var(--vscode-editor-font-family,monospace); padding:2px 6px;border-radius:4px;background:var(--vscode-badge-background);color:var(--vscode-badge-foreground); } +button { display:inline-flex;align-items:center;justify-content:center;color:var(--vscode-descriptionForeground);background:transparent;border:1px solid transparent;border-radius:5px;padding:5px 8px;cursor:pointer;font:inherit;font-size:12px;white-space:nowrap; } +button:hover { background:var(--vscode-toolbar-hoverBackground);color:var(--vscode-foreground); } button:focus-visible { outline:1px solid var(--vscode-focusBorder);outline-offset:2px; } +.graph-stage { position:relative; border:1px solid var(--vscode-panel-border);border-radius:8px;background:var(--vscode-editor-background); } +#graph { overflow:hidden; position:relative; cursor:grab; user-select:none; touch-action:none; min-height:260px; height:calc(100vh - 185px);box-sizing:border-box; } +#graph svg { display:block;position:absolute;left:0;top:0;transform-origin:0 0;height:auto; } +#graph.is-grabbing,#graph.is-grabbing .node { cursor:grabbing; } .zoom { position:absolute;right:12px;bottom:12px;gap:2px;background:var(--vscode-editorWidget-background);border:1px solid var(--vscode-panel-border);border-radius:6px;padding:2px; } +.zoom button { min-width:28px; } #feedback { color:var(--vscode-descriptionForeground);font-size:11px;white-space:nowrap; } +.graph-help { margin-top:12px;color:var(--vscode-descriptionForeground);font-size:11px; } summary { cursor:pointer; } pre { overflow:auto;max-height:240px;line-height:1.6; } .graph-legend { margin:12px 0; } .graph-legend span { border-radius:4px;padding:3px 7px; } +#graph svg .node rect { filter:none !important;stroke-width:1.25px; } +#graph .node.project rect { fill:color-mix(in srgb,var(--vscode-charts-blue) 13%,var(--vscode-editor-background));stroke:var(--vscode-charts-blue) !important; } +#graph .node.manifest rect { fill:color-mix(in srgb,var(--vscode-charts-purple) 10%,var(--vscode-editor-background));stroke:var(--vscode-charts-purple) !important; } +#graph .node.index rect { fill:color-mix(in srgb,var(--vscode-charts-green) 10%,var(--vscode-editor-background));stroke:var(--vscode-charts-green) !important; } +#graph .node.unknown rect { fill:color-mix(in srgb,var(--vscode-charts-orange) 8%,var(--vscode-editor-background));stroke:var(--vscode-charts-orange) !important;stroke-dasharray:4 3; } +#graph .node text { fill:var(--vscode-foreground);font-size:13px; } #graph .node text>tspan:first-child { font-weight:600; } +#graph .node text>tspan:nth-child(n+2) { fill:var(--vscode-descriptionForeground);font-size:11px; } +#graph .node { cursor:pointer; } #graph .node.is-focused rect,#graph .node:focus-visible rect { stroke:var(--vscode-focusBorder) !important;stroke-width:3px !important;filter:drop-shadow(0 0 5px var(--vscode-focusBorder)) !important; } +#graph .edgeLabel { color:var(--vscode-descriptionForeground);font-size:11px; } #graph .edgeLabel text { fill:var(--vscode-descriptionForeground); } #graph .edgeLabel .labelBkg { background:var(--vscode-editor-background);fill:var(--vscode-editor-background); } +#graph .edgePath path { stroke:var(--vscode-descriptionForeground); } .legend-project { color:var(--vscode-charts-blue); } .legend-path { color:var(--vscode-charts-purple); } .legend-index { color:var(--vscode-charts-green); } .legend-unknown { color:var(--vscode-charts-orange); } +.vscode-high-contrast button,.vscode-high-contrast-light button { border-color:var(--vscode-contrastBorder); } +@media(max-width:600px) { body { padding:16px 12px; } .graph-header { flex-wrap:wrap; } } + +#graph .edgeLabel rect.background { fill:var(--vscode-editor-background) !important;opacity:1 !important; } +#graph .node text>tspan:first-child .text-inner-tspan { font-weight:600 !important; } +#graph .node text>tspan:nth-child(n+2) { font-size:11px !important; } + +.graph-tools { display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px;font-size:12px; } +.graph-tabs { display:flex;gap:2px;padding:2px;border:1px solid var(--vscode-panel-border);border-radius:6px; } +.graph-tabs [aria-pressed=true] { color:var(--vscode-foreground);background:var(--vscode-toolbar-activeBackground); } +.graph-tools input,.graph-tools select { font:inherit;background:var(--vscode-input-background);color:var(--vscode-input-foreground);border:1px solid var(--vscode-input-border,transparent);padding:4px 7px;border-radius:4px; } +.graph-tools input { width:180px;min-width:100px; } .graph-tools input:focus,.graph-tools select:focus { outline:1px solid var(--vscode-focusBorder); } +.graph-selection { min-height:30px;display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:8px;color:var(--vscode-descriptionForeground);font-size:12px; } +#selected-node { overflow:hidden;white-space:nowrap;text-overflow:ellipsis; } button:disabled { opacity:.4;cursor:default; } +#graph { height:calc(100vh - 245px); } +#graph.is-loading { visibility:hidden; } #graph-state { position:absolute;top:50%;left:24px;right:24px;transform:translateY(-50%);text-align:center;color:var(--vscode-descriptionForeground);font-size:13px;white-space:pre-wrap; } +#graph-state[hidden], [hidden] { display:none !important; } #graph-notice { color:var(--vscode-descriptionForeground);font-size:11px;margin:8px 0; } +#graph .fold-control,#graph .fold-control tspan { fill:var(--vscode-textLink-foreground);cursor:pointer;font-weight:600; } +#graph .fold-control:hover { text-decoration:underline; } #graph .fold-control:focus-visible { outline:1px solid var(--vscode-focusBorder); } +@media(max-width:600px) { .graph-selection { flex-wrap:wrap; } .graph-tools { gap:8px; } #graph { height:calc(100vh - 290px); } } diff --git a/media/graph.js b/media/graph.js new file mode 100644 index 0000000..36b5176 --- /dev/null +++ b/media/graph.js @@ -0,0 +1,136 @@ +/* Shared package/module graph canvas. Providers send bounded subgraphs; the canvas owns navigation. */ +(() => { + const vscode = acquireVsCodeApi(), labels = JSON.parse(document.getElementById('graph-labels').textContent); + const graph = document.getElementById('graph'), status = document.getElementById('graph-state'); + const views = new Map(); + let data, currentKey, selected, zoom = 1, panX = 0, panY = 0, baseWidth = 0, baseHeight = 0; + let drag, suppressClick = false, pending, drawing = false, renderId = 0, busy = true; + const send = (action, extra = {}) => vscode.postMessage({ type: 'action', action, viewKey: currentKey, ...extra }); + function applyView() { const svg = graph.querySelector('svg'); if (svg) svg.style.transform = `translate(${panX}px,${panY}px) scale(${zoom})`; } + function saveView() { if (currentKey && baseWidth) views.set(currentKey, { zoom, panX, panY }); } + function zoomAt(next, x = graph.clientWidth / 2, y = graph.clientHeight / 2) { + next = Math.max(.02, Math.min(4, next)); const ratio = next / zoom; + panX = x - (x - panX) * ratio; panY = y - (y - panY) * ratio; zoom = next; applyView(); saveView(); + } + function fitGraph() { + if (!baseWidth || !baseHeight) return; + zoom = Math.max(.001, Math.min(1, (graph.clientWidth - 48) / baseWidth, (graph.clientHeight - 48) / baseHeight)); + panX = (graph.clientWidth - baseWidth * zoom) / 2; panY = (graph.clientHeight - baseHeight * zoom) / 2; applyView(); saveView(); + } + function selectNode(id, center = false) { + selected = id; + graph.querySelectorAll('.node').forEach(node => { + const active = node.dataset.nodeKey === id; + node.classList.toggle('is-focused', active); node.setAttribute('aria-pressed', String(active)); + if (active && center) { + if (zoom < .85) zoomAt(.85); + const viewport = graph.getBoundingClientRect(), box = node.getBoundingClientRect(); + panX += viewport.left + viewport.width / 2 - box.left - box.width / 2; + panY += viewport.top + viewport.height / 2 - box.top - box.height / 2; applyView(); saveView(); + } + }); + document.getElementById('selected-node').textContent = data?.nodes.find(node => node.id === id)?.label || labels.selected; + for (const button of document.querySelectorAll('.graph-selection button')) button.disabled = !id || busy; + } + function activateNode(id) { selectNode(id); send('open', { id }); } + function setStatus(text, block = false) { status.textContent = text; status.hidden = !text; graph.classList.toggle('is-loading', block); } + document.querySelectorAll('[data-action]').forEach(button => button.addEventListener('click', () => { + if (button.dataset.action === 'copy') return send('copy'); + send(button.dataset.action, { id: selected }); + })); + document.querySelectorAll('[data-mode]').forEach(button => button.addEventListener('click', () => send('mode', { mode: button.dataset.mode }))); + document.getElementById('graph-depth').addEventListener('change', event => send('depth', { depth: event.target.value })); + const search = document.getElementById('graph-search'); + function find() { const node = data?.search.find(node => node.id === search.value || node.label === search.value); if (node) send('search', { id: node.id }); } + search.addEventListener('change', find); search.addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); find(); } }); + document.querySelectorAll('[data-zoom]').forEach(button => button.addEventListener('click', () => button.dataset.zoom === 'fit' ? fitGraph() : zoomAt(zoom * (button.dataset.zoom === '+' ? 1.2 : 1 / 1.2)))); + graph.addEventListener('wheel', event => { + event.preventDefault(); if (busy) return; + const rect = graph.getBoundingClientRect(), delta = event.deltaY * (event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? graph.clientHeight : 1); + zoomAt(zoom * Math.exp(-delta * .0015), event.clientX - rect.left, event.clientY - rect.top); + }, { passive: false }); + graph.addEventListener('pointerdown', event => { if (!busy && event.button === 0) drag = { id: event.pointerId, x: event.clientX, y: event.clientY, panX, panY, moved: false }; }); + graph.addEventListener('pointermove', event => { + if (!drag || event.pointerId !== drag.id) return; + const dx = event.clientX - drag.x, dy = event.clientY - drag.y; + if (!drag.moved && Math.hypot(dx, dy) > 4) { drag.moved = true; graph.setPointerCapture(event.pointerId); graph.classList.add('is-grabbing'); } + if (drag.moved) { event.preventDefault(); panX = drag.panX + dx; panY = drag.panY + dy; applyView(); saveView(); } + }); + function endDrag(event) { + if (!drag || event.pointerId !== drag.id) return; + suppressClick = drag.moved; if (graph.hasPointerCapture(event.pointerId)) graph.releasePointerCapture(event.pointerId); + graph.classList.remove('is-grabbing'); drag = undefined; setTimeout(() => suppressClick = false, 0); + } + graph.addEventListener('pointerup', endDrag); graph.addEventListener('pointercancel', endDrag); + graph.addEventListener('click', event => { if (suppressClick || busy) { event.preventDefault(); event.stopPropagation(); } }, true); + graph.addEventListener('keydown', event => { + if (['+', '=', '-'].includes(event.key)) { event.preventDefault(); zoomAt(zoom * (event.key === '-' ? 1 / 1.2 : 1.2)); } + if (event.key === '0') { event.preventDefault(); fitGraph(); } + }); + async function draw() { + if (drawing) return; + drawing = true; + while (pending) { + const next = pending; pending = undefined; + const request = ++renderId, viewChanged = currentKey !== next.viewKey; + saveView(); currentKey = next.viewKey; data = next; busy = true; + document.getElementById('project-name').textContent = next.name; + document.getElementById('graph-depth').value = next.depth; + document.querySelectorAll('[data-mode]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.mode === next.mode))); + document.querySelector('[data-action=overview]').hidden = !next.focus; + const options = document.getElementById('graph-options'); options.replaceChildren(); + for (const node of next.search) { const option = document.createElement('option'); option.value = node.label; option.label = node.label; options.append(option); } + if (viewChanged) search.value = ''; + document.getElementById('graph-source').textContent = next.source; + const notice = document.getElementById('graph-notice'); notice.textContent = [next.limited ? labels.limited : '', next.truncated ? labels.partial : ''].filter(Boolean).join(' '); notice.hidden = !notice.textContent; + if (!next.nodes.length) { graph.replaceChildren(); setStatus(labels.empty); busy = false; selectNode(undefined); continue; } + const dark = document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast'); + mermaid.initialize({ startOnLoad: false, htmlLabels: false, securityLevel: 'strict', suppressErrorRendering: true, theme: dark ? 'dark' : 'default', fontFamily: 'system-ui', markdownAutoWrap: false, flowchart: { htmlLabels: false, useMaxWidth: true, wrappingWidth: 1000, nodeSpacing: 30, rankSpacing: 90, padding: 18 }, maxEdges: 310 }); + try { + const result = await mermaid.render(`dependency-svg-${request}`, next.source); + if (pending || data !== next) continue; + graph.innerHTML = result.svg; const svg = graph.querySelector('svg'); + baseWidth = svg.viewBox.baseVal.width; baseHeight = svg.viewBox.baseVal.height; svg.style.width = baseWidth + 'px'; svg.style.maxWidth = 'none'; + const saved = views.get(currentKey); + if (!saved || next.fit) fitGraph(); else { ({ zoom, panX, panY } = saved); applyView(); } + graph.querySelectorAll('.edgeLabel .label').forEach(label => { + const text = label.querySelector('text'), background = label.querySelector('rect.background'); + if (text && background) { const box = text.getBBox(); for (const [key, value] of Object.entries({ x: box.x - 4, y: box.y - 3, width: box.width + 8, height: box.height + 6, rx: 3 })) background.setAttribute(key, String(value)); } + }); + graph.querySelectorAll('.node').forEach(node => { + const match = /flowchart-(n[0-9]+)-/.exec(node.id); if (!match) return; + const item = next.nodes[Number(match[1].slice(1))]; if (!item) return; + node.dataset.nodeKey = item.id; node.setAttribute('tabindex', '0'); node.setAttribute('role', 'button'); node.setAttribute('aria-label', node.textContent); + node.querySelectorAll('rect').forEach(rect => { rect.setAttribute('rx', '8'); rect.setAttribute('ry', '8'); }); + node.addEventListener('click', () => activateNode(item.id)); + node.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); activateNode(item.id); } }); + if (item.hidden) { + const line = node.querySelector('text')?.lastElementChild; + if (line) { + line.classList.add('fold-control'); line.setAttribute('role', 'button'); line.setAttribute('tabindex', '0'); line.setAttribute('aria-label', labels.expand + ': ' + item.hidden); + const expand = event => { event.preventDefault(); event.stopPropagation(); selectNode(item.id); send('expand', { id: item.id }); }; + line.addEventListener('click', expand); line.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') expand(event); }); + } + } + }); + busy = false; setStatus(''); selectNode(next.selected, next.center); saveView(); + } catch { busy = false; graph.replaceChildren(); setStatus(labels.error); } + } + drawing = false; + } + window.addEventListener('message', event => { + const message = event.data; + if (message.type === 'graph') { pending = message; draw(); } + if (message.type === 'loading') { + saveView(); currentKey = message.viewKey; baseWidth = 0; data = undefined; pending = undefined; busy = true; + document.getElementById('project-name').textContent = message.name; + document.querySelectorAll('[data-mode]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.mode === message.mode))); + search.value = ''; selectNode(undefined); setStatus(labels.loading, true); + } + if (message.type === 'error') { busy = false; setStatus(message.error, true); } + if (message.type === 'copied') { const feedback = document.getElementById('feedback'); feedback.textContent = labels.copied; setTimeout(() => feedback.textContent = '', 1800); } + }); + new MutationObserver(() => { if (data) { pending = data; draw(); } }).observe(document.body, { attributes: true, attributeFilter: ['class'] }); + window.addEventListener('resize', () => applyView()); + selectNode(undefined); setStatus(labels.loading, true); vscode.postMessage({ type: 'ready' }); +})(); diff --git a/media/library.css b/media/library.css index 7d59070..be2d241 100644 --- a/media/library.css +++ b/media/library.css @@ -283,8 +283,9 @@ main { body.detail { background-color: var(--vscode-editor-background); - max-width: 900px; - padding: 0 20px 40px; + width: 100%; + min-width: 0; + padding: 0 clamp(16px, 2vw, 40px) 40px; } .detail-header { @@ -442,6 +443,8 @@ body.detail { margin: 0; padding: 8px 10px; overflow-x: auto; + max-height: min(52vh, 440px); + overflow-y: auto; background-color: var(--vscode-textCodeBlock-background, transparent); border: 1px solid var(--vscode-panel-border, transparent); border-radius: 3px; @@ -465,6 +468,11 @@ body.detail { } pre.detail-code code { + display: block; + background: transparent; + color: inherit; + padding: 0; + border-radius: 0; font-family: var(--vscode-editor-font-family, monospace); font-size: 12px; line-height: 1.5; @@ -630,3 +638,108 @@ pre.detail-code code { font-size: 10px; border-top: 1px solid var(--vscode-panel-border, transparent); } + +/* Libraries: keep the search control stable while replacing results in place. */ +.toolbar { position: sticky; } +.search { padding-right: 24px; } +.clear-search { position: absolute; right: 18px; top: 10px; font-size: 16px; } +.row[aria-current="true"] { background: var(--vscode-list-inactiveSelectionBackground); box-shadow: inset 2px 0 var(--vscode-focusBorder); } +.group-title { margin: 12px 10px 4px; font-size: 11px; color: var(--vscode-descriptionForeground); } +.search-status { margin: 8px 10px; color: var(--vscode-descriptionForeground); } +.footer details { margin-bottom: 6px; } +.footer summary, .detail-footer summary, .detail-aside summary { cursor: pointer; } +.footer-actions { display: flex; flex-wrap: wrap; gap: 6px 12px; } +.source { white-space: pre-wrap; } +.project-notice { margin-top: 2px; } + +/* Use the editor width; reserve a compact metadata column instead of scaling it with the code. */ +.detail-title { overflow-wrap: anywhere; } +.detail-primary { margin: 0; padding: 8px 0 10px; } +.project-context, .detail-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 8px 0; } +.project-context { color: var(--vscode-descriptionForeground); } +.project-context strong { font-weight: 500; overflow-wrap: anywhere; } +.detail-controls label { display: flex; align-items: center; gap: 6px; } +.detail-controls select { padding: 3px 8px; font: inherit; color: var(--vscode-dropdown-foreground); background: var(--vscode-dropdown-background); border: 1px solid var(--vscode-dropdown-border, var(--vscode-input-border, transparent)); } +.detail-layout { display: grid; grid-template-columns: minmax(0, 1fr) clamp(240px, 24vw, 320px); gap: 28px; } +.detail-layout > *, .detail-command-row > *, .detail-usage-line > * { min-width: 0; } +.detail-aside { min-width: 0; } +.detail-aside .detail-version-group { display: block; } +.detail-aside .detail-platform { display: block; margin-bottom: 4px; } +.detail-version-list { display: flex; flex-wrap: wrap; gap: 4px; } +span.detail-version { cursor: default; color: var(--vscode-descriptionForeground); background: var(--vscode-textCodeBlock-background); border-color: transparent; } +span.detail-version[data-installed] { color: var(--vscode-testing-iconPassed, var(--vscode-foreground)); } +.detail-usage > h2:not(:first-child) { margin-top: 20px; } +.detail-command { white-space: pre; } +.detail-command-row { min-width: 0; } +.detail-command-row .detail-command { flex: 1; } +.detail-command-row .detail-copy { flex-shrink: 0; } +.example-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 20px 0 8px; } +.example-heading h2 { margin: 0; min-width: 0; overflow-wrap: anywhere; } +.snippet-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; } +.snippet-action { display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 4px 7px; border: 1px solid transparent; border-radius: 4px; color: var(--vscode-descriptionForeground); background: transparent; font: inherit; font-size: 11px; cursor: pointer; } +.snippet-action:hover { background: var(--vscode-toolbar-hoverBackground); color: var(--vscode-foreground); } +.snippet-action:focus-visible { outline: 1px solid var(--vscode-focusBorder); outline-offset: 1px; } +.vscode-high-contrast .snippet-action, .vscode-high-contrast-light .snippet-action { border-color: var(--vscode-contrastBorder); } +.result-actions { display: flex; flex-wrap: wrap; gap: 12px; } +button:disabled, select:disabled { cursor: default; opacity: 0.65; } +button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible { outline: 1px solid var(--vscode-focusBorder); outline-offset: 2px; } +.detail-footer { overflow-wrap: anywhere; } +@media (max-width: 899px) { + .detail-layout { display: block; } + body.detail { padding-left: 16px; padding-right: 16px; } +} +@media (max-width: 520px) { + .detail-actions { gap: 8px; } + .detail-actions button { padding: 5px 8px; } + .detail-command-row { flex-wrap: wrap; } + .detail-command-row .detail-command { flex-basis: 100%; } +} + +.toolbar .count { margin: 0 0 0 auto; align-self: center; } +.detail-actions .link-button { color: var(--vscode-textLink-foreground); background: transparent; border-color: transparent; padding: 4px 0; } +.detail-actions .link-button:hover { background: transparent; text-decoration: underline; } + +/* A single, compact context row; empty feedback reserves no vertical space. */ +.project-context { margin: 0 0 8px; gap: 8px; font-size: 11px; } +.project-picker { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; margin: 0; padding: 2px 0; font: inherit; color: var(--vscode-descriptionForeground); background: transparent; border: 0; cursor: pointer; } +.project-picker strong { color: var(--vscode-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.project-picker:hover strong { color: var(--vscode-textLink-foreground); } +.context-platform::before { content: "·"; margin-right: 8px; } +.detail-controls { margin: 4px 0 10px; gap: 8px 14px; } +.detail-command-row { margin-top: 8px; } +.detail-command { margin: 0; } +.detail-result { margin: 8px 0 0; } +.detail-layout { gap: 24px; } +.detail-aside .detail-section { padding: 10px 0; margin: 0; } +.detail-aside summary { font-size: 12px; font-weight: 500; } +.detail-aside details[open] > summary { margin-bottom: 10px; } +.detail-usage { padding-top: 12px; } +.detail-footer { margin-top: 14px; } + +/* Package identity and context share one header; tags remain readable in every theme. */ +.detail-heading-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 10px 18px; } +.detail-heading { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; min-width: 0; } +.detail-heading .detail-title { max-width: 100%; } +.detail-heading-row .project-context { justify-content: flex-end; max-width: 220px; margin: 0; padding-top: 2px; } +.detail-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; } +.detail-tag { display: inline-block; padding: 1px 6px; border: 1px solid currentColor; border-radius: 4px; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; line-height: 1.6; font-weight: 500; } +.detail-tag[data-tag="surface"] { color: var(--vscode-symbolIcon-interfaceForeground, var(--vscode-textLink-foreground)); background: color-mix(in srgb, var(--vscode-symbolIcon-interfaceForeground, var(--vscode-textLink-foreground)) 12%, transparent); } +.detail-tag[data-tag="standard"] { color: var(--vscode-charts-purple, var(--vscode-textLink-foreground)); background: color-mix(in srgb, var(--vscode-charts-purple, var(--vscode-textLink-foreground)) 12%, transparent); } +.detail-tag[data-tag="license"] { color: var(--vscode-charts-green, var(--vscode-foreground)); background: color-mix(in srgb, var(--vscode-charts-green, var(--vscode-foreground)) 12%, transparent); } +.vscode-high-contrast .detail-tag, .vscode-high-contrast-light .detail-tag { background: transparent; color: var(--vscode-foreground); } +.dependency-kind-label { color: var(--vscode-descriptionForeground); font-family: var(--vscode-editor-font-family); font-size: 11px; } +.detail-add-group { position: relative; display: inline-flex; align-items: stretch; } +.detail-add-group .detail-add { border-radius: 2px 0 0 2px; } +.detail-add-group .detail-kind-toggle { width: 28px; min-width: 28px; padding: 4px; border-radius: 0 2px 2px 0; border-left-color: var(--vscode-button-separator, var(--vscode-contrastBorder, transparent)); } +.detail-kind-menu { position: absolute; z-index: 10; top: calc(100% + 4px); left: 0; width: 270px; max-width: calc(100vw - 36px); padding: 4px; border: 1px solid var(--vscode-widget-border, var(--vscode-contrastBorder, transparent)); border-radius: 4px; background: var(--vscode-menu-background, var(--vscode-editorWidget-background)); box-shadow: 0 4px 12px var(--vscode-widget-shadow, transparent); } +.detail-actions .detail-kind-menu button { position: relative; display: grid; width: 100%; gap: 2px; padding: 7px 10px 7px 26px; text-align: left; color: var(--vscode-menu-foreground, var(--vscode-foreground)); background: transparent; border: 0; border-radius: 2px; } +.detail-kind-menu button[aria-checked="true"]::before { content: "✓"; position: absolute; left: 7px; top: 7px; } +.detail-kind-menu code { font-size: 10px; color: var(--vscode-descriptionForeground); } +.detail-actions .detail-kind-menu button:hover, .detail-actions .detail-kind-menu button:focus { color: var(--vscode-menu-selectionForeground, var(--vscode-list-activeSelectionForeground)); background: var(--vscode-menu-selectionBackground, var(--vscode-list-activeSelectionBackground)); } +@media (max-width: 520px) { + .detail-heading-row { display: flex; flex-direction: column; gap: 4px; } + .detail-heading-row .project-context { align-self: flex-end; } +} + +.detail-kind-menu code { background: transparent; padding: 0; } +.detail-kind-menu button:hover code, .detail-kind-menu button:focus code { color: inherit; } diff --git a/package-lock.json b/package-lock.json index 562b7fd..0c4291b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "mcpp-vscode", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcpp-vscode", - "version": "0.7.0", + "version": "0.8.0", "license": "Apache-2.0", "devDependencies": { + "@mermaid-js/tiny": "12.2.0", "@types/mocha": "^10.0.10", "@types/node": "^24.0.0", "@types/vscode": "^1.91.0", @@ -722,6 +723,16 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@mermaid-js/tiny": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/tiny/-/tiny-12.2.0.tgz", + "integrity": "sha512-9bEE+R974NTh1YJlfc6ztgwhy6+9sE+wAFiFrqvSdtlJHdxGfLytQmHYHNpFKmj94+Z+uMicYrQeGXnTTjILAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/@napi-rs/keyring": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@napi-rs/keyring/-/keyring-1.3.0.tgz", diff --git a/package.json b/package.json index 1e279de..36a6050 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mcpp-vscode", "displayName": "%displayName%", "description": "%description%", - "version": "0.7.0", + "version": "0.8.0", "publisher": "mcpp-community", "license": "Apache-2.0", "icon": "media/logo.png", @@ -75,6 +75,11 @@ { "command": "mcpp.languageServer.restoreConflicts", "when": "view == mcpp.project && viewItem =~ /^mcppOtherCppFeatures.*Restorable$/" + }, + { + "command": "mcpp.project.showDependencyGraph", + "when": "view == mcpp.project && viewItem == mcppProjectDependencies", + "group": "inline@1" } ] }, @@ -315,6 +320,18 @@ { "command": "mcpp.library.updateIndex", "title": "%command.mcpp.library.updateIndex.title%", + "category": "%category%", + "icon": "$(cloud-download)" + }, + { + "command": "mcpp.project.showDependencyGraph", + "title": "%command.mcpp.project.showDependencyGraph.title%", + "category": "%category%", + "icon": "$(type-hierarchy)" + }, + { + "command": "mcpp.project.showModuleDependencyGraph", + "title": "%command.mcpp.project.showModuleDependencyGraph.title%", "category": "%category%" } ], @@ -1150,7 +1167,7 @@ }, "scripts": { "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"", - "compile": "tsc -p tsconfig.json", + "compile": "tsc -p tsconfig.json && node tools/copy-webview-assets.mjs", "test": "npm run check && npm run clean && npm run compile && node --test \"dist/test/**/*.test.js\"", "package": "npm run clean && npm run compile && vsce package", "test:e2e": "npm run compile && node dist/test/e2e/runTest.js", @@ -1166,9 +1183,11 @@ "gen:menuicons": "node tools/generate-quick-menu-icons.mjs", "check:icon": "node tools/generate-activitybar-icon.mjs --check && node tools/generate-quick-menu-icons.mjs --check", "check:generated": "node tools/check-generators.mjs", - "test:e2e:one": "node dist/test/e2e/runTest.js" + "test:e2e:one": "node dist/test/e2e/runTest.js", + "gen:examples": "node tools/generate-library-examples.mjs" }, "devDependencies": { + "@mermaid-js/tiny": "12.2.0", "@types/mocha": "^10.0.10", "@types/node": "^24.0.0", "@types/vscode": "^1.91.0", diff --git a/package.nls.json b/package.nls.json index eef1192..d1aab1b 100644 --- a/package.nls.json +++ b/package.nls.json @@ -101,9 +101,9 @@ "mcpp.languageService.refreshAfterBuild.title": "Refresh language service after build", "mcpp.languageService.stateRefreshSeconds.description": "Re-read the language service state every this many seconds. 0 disables the timer.", "mcpp.languageService.stateRefreshSeconds.title": "Set the state refresh interval", - "mcpp.library.indexPath.description": "Leave it empty to use the indexes under the mcpp home. The home is `$MCPP_HOME`, then `~/.mcpp`; when neither holds an index, the extension asks `mcpp self env --format json` where it is.", + "mcpp.library.indexPath.description": "Leave empty to browse the official mcpp-index at /indices/mcpplibs (legacy registry/data/mcpplibs is supported). MCPP_HOME takes precedence; otherwise the extension asks mcpp self env. Set a directory containing pkgs/ or a collection of indexes to browse a custom source.", "mcpp.library.indexPath.title": "Path to a mcpp package index checkout", - "mcpp.library.networkSearch.description": "The local index already lists every published package, so this is off by default. Turning it on also searches the other registries with `mcpp search`, which may use the network and can only be parsed on a best-effort basis.", + "mcpp.library.networkSearch.description": "Off by default. Also searches other registries with mcpp search; remote results have limited previews and cannot be added directly.", "mcpp.library.networkSearch.title": "Search every configured registry over the network", "mcpp.log.level.description": "Amount of detail written to the mcpp output channel. Debug output is verbose.", "mcpp.log.level.title": "Set the log level", @@ -204,5 +204,7 @@ "view.mcpp.library.name": "Libraries", "view.mcpp.project.name": "Project", "viewsContainer.mcpp.title": "mcpp", - "viewsWelcome.library": "No mcpp package index was found on this machine.\n\nRun `mcpp index update` once, or set `mcpp.library.indexPath` to an index checkout.\n[Run mcpp index update](command:mcpp.library.updateIndex)" + "viewsWelcome.library": "No mcpp package index was found on this machine.\n\nRun `mcpp index update` once, or set `mcpp.library.indexPath` to an index checkout.\n[Run mcpp index update](command:mcpp.library.updateIndex)", + "command.mcpp.project.showDependencyGraph.title": "Show Project Dependency Graph", + "command.mcpp.project.showModuleDependencyGraph.title": "Show C++ Modules Dependency Graph" } diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 33cc3ad..8f6daf9 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -101,9 +101,9 @@ "mcpp.languageService.refreshAfterBuild.title": "构建后刷新语言服务", "mcpp.languageService.stateRefreshSeconds.description": "每隔多少秒重新读取一次语言服务状态。0 表示关闭定时刷新。", "mcpp.languageService.stateRefreshSeconds.title": "设置状态刷新间隔", - "mcpp.library.indexPath.description": "留空则使用 mcpp 主目录下的索引。主目录依次取 `$MCPP_HOME`、`~/.mcpp`;两者都没有索引时,扩展会用 `mcpp self env --format json` 询问 mcpp 主目录在哪。", + "mcpp.library.indexPath.description": "留空则读取 mcpp 主目录下 indices/mcpplibs 中的官方 mcpp-index,兼容旧位置 registry/data/mcpplibs。优先使用 MCPP_HOME,否则通过 mcpp self env 获取主目录。可指定带 pkgs/ 的目录或索引集合目录来浏览自定义来源。", "mcpp.library.indexPath.title": "mcpp 包索引检出的路径", - "mcpp.library.networkSearch.description": "本地索引已经列出所有已发布的包,所以默认关闭。打开后会额外用 `mcpp search` 搜索其它 registry,可能联网,且只能尽力解析。", + "mcpp.library.networkSearch.description": "默认关闭。开启后额外用 mcpp search 搜索其它 registry;搜索结果仅提供有限预览,不能直接添加。", "mcpp.library.networkSearch.title": "在全部 registry 中联网搜索", "mcpp.log.level.description": "写入 mcpp 输出频道的详细程度。debug 输出较为冗长。", "mcpp.log.level.title": "设置日志级别", @@ -204,5 +204,7 @@ "view.mcpp.library.name": "库生态", "view.mcpp.project.name": "工程", "viewsContainer.mcpp.title": "mcpp", - "viewsWelcome.library": "本机没有找到 mcpp 包索引。\n\n先运行一次 `mcpp index update`,或用 `mcpp.library.indexPath` 指向一个索引检出。\n[运行 mcpp index update](command:mcpp.library.updateIndex)" + "viewsWelcome.library": "本机没有找到 mcpp 包索引。\n\n先运行一次 `mcpp index update`,或用 `mcpp.library.indexPath` 指向一个索引检出。\n[运行 mcpp index update](command:mcpp.library.updateIndex)", + "command.mcpp.project.showDependencyGraph.title": "显示项目依赖图", + "command.mcpp.project.showModuleDependencyGraph.title": "显示 C++ Modules 依赖图" } diff --git a/src/cli/moduleGraph.ts b/src/cli/moduleGraph.ts new file mode 100644 index 0000000..9f43c03 --- /dev/null +++ b/src/cli/moduleGraph.ts @@ -0,0 +1,10 @@ +import { runProcess, type ProcessRunner } from "./process"; +import { parseModuleGraph, type ModuleGraphJson } from "../projects/moduleGraph"; + +/** Use mcppls's resolved graph; never launch a second C++ parser in this extension. */ +export async function queryModuleGraph(trusted: boolean, executable: string, root: string, runner: ProcessRunner = runProcess): Promise { + if (!trusted) { throw new Error("Trust this workspace before reading its module graph."); } + const result = await runner(executable, ["query", "module", "--graph", "--root", root, "--timeout", "20", "--format", "json"], root, { timeoutMs: 30_000, maxBufferMiB: 8 }); + if (result.exitCode !== 0) { throw new Error(result.stderr.trim() || "mcppls module graph query failed"); } + return parseModuleGraph(result.stdout); +} diff --git a/src/commands/ids.ts b/src/commands/ids.ts index f0a5b12..811bd74 100644 --- a/src/commands/ids.ts +++ b/src/commands/ids.ts @@ -81,6 +81,8 @@ export const LIBRARY_COMMANDS = { /** Settings, diagnostics and the mcpp CLI entry points added in 0.5.0. */ export const TOOL_COMMANDS = { + dependencyGraph: "mcpp.project.showDependencyGraph", + moduleDependencyGraph: "mcpp.project.showModuleDependencyGraph", openSettings: "mcpp.openSettings", openLanguageServerSettings: "mcpp.openMcpplsSettings", selfCheck: "mcpp.selfCheck", diff --git a/src/extension.ts b/src/extension.ts index f4ab0ec..0c2fa67 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -40,7 +40,8 @@ import { languagePreference, setLanguagePreference, t, type LanguagePreference } import { readCacheSnapshot, registerCacheView } from "./cache/cacheView"; import { registerLanguageServerCommands } from "./views/languageServerView"; import { createLibraryDetailOpener } from "./library/detailPanel"; -import { loadSnapshot } from "./library/indexLocator"; +import { loadSnapshot, invalidateIndexCache } from "./library/indexLocator"; +import { watchProjectFiles } from "./library/projectWatcher"; import { registerLibraryView } from "./library/libraryView"; import { registerProjectView } from "./views/projectView"; @@ -322,22 +323,77 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi // The library ecosystem: a sidebar view plus an editor-area package page. The // detail page writes `mcpp.toml` through `mcpp add`, so it tells the view to // refresh — no save event fires for a file written outside the editor. + // Keep the selected library project when an editor-area webview takes focus. + let libraryProjectRoot: string | undefined = vscode.window.activeTextEditor?.document.uri.scheme === "file" + || vscode.workspace.workspaceFolders?.length === 1 ? findCurrentProject()?.root : undefined; + const libraryProject = (): string | undefined => libraryProjectRoot; + const chooseLibraryProject = async (): Promise => { + const manifests = await vscode.workspace.findFiles(MCPP_MANIFEST_GLOB, "**/{node_modules,target,.git}/**", 200); + const roots = [...new Set(manifests.map((uri) => uri.fsPath.slice(0, -"/mcpp.toml".length)))]; + const active = libraryProject(); + if (active && !roots.includes(active)) { roots.unshift(active); } + const selected = await vscode.window.showQuickPick(roots.map((root) => ({ label: vscode.workspace.asRelativePath(root), description: root, root })), { + title: t("Choose the target mcpp project"), placeHolder: t("Dependencies will be added to this project's mcpp.toml"), + }); + if (selected) { libraryProjectRoot = selected.root; watchSelectedLibraryProject(); refreshLibraries(); } + return selected?.root; + }; const openLibraryDetail = createLibraryDetailOpener(extensionContext, { - mcppExecutable: () => cliController.mcppExecutable(findCurrentProject()), - projectRoot: () => findCurrentProject()?.root, + mcppExecutable: (root) => cliController.mcppExecutable(root ? { root, manifestPath: `${root}/mcpp.toml` } : undefined), + projectRoot: libraryProject, + chooseProject: chooseLibraryProject, + onPackageOpened: (key) => library.select(key), output, isTrusted: () => vscode.workspace.isTrusted, - onDependenciesChanged: () => void library.refresh(), + onDependenciesChanged: () => { void library.refresh(); }, + onProjectFilesRead: (root, manifest, lock) => { if (root === libraryProjectRoot) { selectedLibraryPoll?.observe(manifest, lock); } }, }); const library = registerLibraryView(extensionContext, { - projectRoot: () => findCurrentProject()?.root, - mcppExecutable: () => cliController.mcppExecutable(findCurrentProject()), + projectRoot: libraryProject, + mcppExecutable: () => cliController.mcppExecutable(libraryProjectRoot ? { root: libraryProjectRoot, manifestPath: `${libraryProjectRoot}/mcpp.toml` } : undefined), openDetail: openLibraryDetail, output, - // The cross-registry search runs `mcpp search`, and `mcpp.path` is - // resource-scoped: an untrusted workspace must not name the program. isTrusted: () => vscode.workspace.isTrusted, }); + const dependencyWatcher = vscode.workspace.createFileSystemWatcher("**/mcpp.{toml,lock}"); + let selectedLibraryWatcher: vscode.Disposable | undefined; + let selectedLibraryPoll: ReturnType | undefined; + let libraryTimer: NodeJS.Timeout | undefined; + const refreshLibraries = (): void => { + void selectedLibraryPoll?.check(); + if (libraryTimer) { clearTimeout(libraryTimer); } + libraryTimer = setTimeout(() => { + libraryTimer = undefined; + void library.refresh(); + void openLibraryDetail.refresh(); + }, 120); + }; + // A non-recursive RelativePattern also observes scratch/gitignored projects + // which a workspace's recursive watcher may intentionally exclude. + function watchSelectedLibraryProject(): void { + selectedLibraryWatcher?.dispose(); + selectedLibraryWatcher = undefined; + selectedLibraryPoll = undefined; + if (!libraryProjectRoot) { return; } + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.file(libraryProjectRoot), "mcpp.{toml,lock}")); + selectedLibraryPoll = watchProjectFiles(libraryProjectRoot, refreshLibraries); + selectedLibraryWatcher = vscode.Disposable.from(selectedLibraryPoll, watcher, watcher.onDidCreate(refreshLibraries), watcher.onDidChange(refreshLibraries), watcher.onDidDelete(refreshLibraries)); + } + watchSelectedLibraryProject(); + extensionContext.subscriptions.push( + { dispose: () => selectedLibraryWatcher?.dispose() }, + dependencyWatcher, + dependencyWatcher.onDidCreate(refreshLibraries), + dependencyWatcher.onDidChange(refreshLibraries), + dependencyWatcher.onDidDelete(refreshLibraries), + vscode.window.onDidChangeActiveTextEditor((editor) => { if (editor?.document.uri.scheme === "file") { const active = findCurrentProject()?.root; if (active && active !== libraryProjectRoot) { libraryProjectRoot = active; watchSelectedLibraryProject(); } refreshLibraries(); } }), + vscode.workspace.onDidChangeWorkspaceFolders(() => { libraryProjectRoot = undefined; watchSelectedLibraryProject(); refreshLibraries(); }), + vscode.workspace.onDidGrantWorkspaceTrust(refreshLibraries), + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("mcpp.library") || event.affectsConfiguration("mcpp.path") || event.affectsConfiguration("mcpp.ui.language")) { refreshLibraries(); } + }), + { dispose: () => { if (libraryTimer) { clearTimeout(libraryTimer); } } }, + ); // `registerLibraryView` registers its own webview provider; these are only the // entry points a menu or the project view can name. extensionContext.subscriptions.push( @@ -351,41 +407,30 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi }), vscode.commands.registerCommand(LIBRARY_COMMANDS.updateIndex, async () => { if (!vscode.workspace.isTrusted) { - void vscode.window.showWarningMessage( - t("This workspace is not trusted. mcpp commands may run external programs named by workspace settings; trust the workspace first."), - ); + void vscode.window.showWarningMessage(t("This workspace is not trusted. mcpp commands may run external programs named by workspace settings; trust the workspace first.")); return; } - const project = findCurrentProject(); - // A refresh that reaches the network is worth a progress notification, and - // a refresh that succeeds has to *say so*: the command produced no visible - // change at all before, which reads exactly like a broken button. + const root = libraryProject(); + const project = root ? { root, manifestPath: `${root}/mcpp.toml` } : undefined; + if (read("mcpp.library.indexPath").trim()) { + output.appendLine(t("Updating the global mcpp index; a configured custom checkout is only reread.")); + } + library.setUpdateStatus(t("Updating mcpp index…")); const result = await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: t("Refreshing the mcpp Package Index…") }, - () => - runMcpp(vscode.workspace.isTrusted, cliController.mcppExecutable(project), ["index", "update"], project?.root, { - timeoutMs: 300_000, - }), - ); - await library.refresh(); - if (result === undefined || result.exitCode !== 0) { - void vscode.window.showErrorMessage( - result === undefined - ? t("This workspace is not trusted. mcpp commands may run external programs named by workspace settings; trust the workspace first.") - : t("mcpp index update failed with exit code {0}", result.exitCode), - ); - return; - } - // `library.refresh()` has just filled the snapshot cache, so this second - // look is the count the view is showing, not another read of the index. - const snapshot = await loadSnapshot(project === undefined ? {} : { projectRoot: project.root }); - void vscode.window.showInformationMessage( - t( - "mcpp Package Index refreshed: {0} package(s) from {1} index folder(s).", - snapshot.entries.length, - snapshot.roots.length, - ), + () => runMcpp(vscode.workspace.isTrusted, cliController.mcppExecutable(project), ["index", "update"], project?.root, { timeoutMs: 300_000 }), ); + if (result === undefined || result.exitCode !== 0) { + library.setUpdateStatus(t("Index update failed; showing the last available local snapshot.")); + if (result?.stderr) { output.appendLine(result.stderr.trimEnd()); } + void vscode.window.showErrorMessage(t("mcpp index update failed with exit code {0}", result?.exitCode ?? -1)); + return; + } + invalidateIndexCache(); + await library.refresh(); + await openLibraryDetail.refresh(); + const snapshot = await loadSnapshot({ projectRoot: root }); + library.setUpdateStatus(t("Index updated; {0} local packages.", snapshot.entries.length)); }), ); // There is no `mcpp.library.indexFound` context key and no `viewsWelcome` diff --git a/src/graphs/model.ts b/src/graphs/model.ts new file mode 100644 index 0000000..a998b59 --- /dev/null +++ b/src/graphs/model.ts @@ -0,0 +1,92 @@ +/** Shared presentation model; providers own package/module semantics and source access. */ +export interface VisualNode { + id: string; + label: string; + lines: string[]; + kind: "project" | "manifest" | "index" | "unknown"; + files?: string[]; + entry?: boolean; +} +export interface VisualEdge { from: string; to: string; label?: string; dashed?: boolean; } +export interface VisualGraph { nodes: VisualNode[]; edges: VisualEdge[]; roots: string[]; truncated?: boolean; } +export interface GraphExpansion { depth: number; expanded: ReadonlySet; collapsed: ReadonlySet; focus?: string; } +export interface VisibleGraph extends VisualGraph { hidden: Map; limited: boolean; } +export const GRAPH_BUDGET = { nodes: 150, edges: 300 } as const; + +/** Choose a root for every directed component, including disconnected cycles. */ +export function graphRoots(nodes: readonly VisualNode[], edges: readonly VisualEdge[], preferred: string[] = []): string[] { + const adj = adjacency(edges), incoming = new Set(edges.map(edge => edge.to)); + const roots: string[] = [], seen = new Set(), known = new Set(nodes.map(node => node.id)); + const visit = (id: string): void => { + if (!known.has(id) || seen.has(id)) { return; } + roots.push(id); + const stack = [id]; + while (stack.length) { + const next = stack.pop()!; + if (seen.has(next)) { continue; } + seen.add(next); + for (const child of adj.get(next) ?? []) { stack.push(child); } + } + }; + for (const id of preferred) { visit(id); } + for (const node of nodes) { if (!incoming.has(node.id)) { visit(node.id); } } + for (const node of nodes) { visit(node.id); } + return roots; +} +function adjacency(edges: readonly VisualEdge[]): Map { + const result = new Map(); + for (const edge of edges) { + const list = result.get(edge.from) ?? []; + list.push(edge.to); result.set(edge.from, list); + } + return result; +} + +/** Bound the subgraph BEFORE layout. Breadth first keeps wide and deep graphs navigable. */ +export function visibleGraph(graph: VisualGraph, state: GraphExpansion, budget: { nodes: number; edges: number } = GRAPH_BUDGET): VisibleGraph { + const adj = adjacency(graph.edges), known = new Set(graph.nodes.map(node => node.id)); + const seen = new Set(), remaining = new Map(); + const queue: Array<[string, number]> = []; + let limited = false; + const add = (id: string, depth: number): void => { + if (!known.has(id)) { return; } + if (!seen.has(id)) { + if (seen.size >= budget.nodes) { limited = true; return; } + seen.add(id); + } + if ((remaining.get(id) ?? -1) >= depth) { return; } + remaining.set(id, depth); queue.push([id, depth]); + }; + if (state.focus && known.has(state.focus)) { + add(state.focus, state.depth); + // Context includes direct importers without recursively expanding their other dependencies. + for (const edge of graph.edges) { if (edge.to === state.focus) { add(edge.from, 0); } } + } else { for (const root of graph.roots) { add(root, state.depth); } } + for (let cursor = 0; cursor < queue.length; cursor++) { + const [id, depth] = queue[cursor]; + if (state.collapsed.has(id) || (depth <= 0 && !state.expanded.has(id))) { continue; } + for (const child of adj.get(id) ?? []) { add(child, Math.max(0, depth - 1)); } + } + const candidates = graph.edges.filter(edge => seen.has(edge.from) && seen.has(edge.to) && !state.collapsed.has(edge.from)); + const edges = candidates.slice(0, budget.edges); + limited ||= candidates.length > edges.length; + const rendered = new Map>(); + for (const edge of edges) { const list = rendered.get(edge.from) ?? new Set(); list.add(edge.to); rendered.set(edge.from, list); } + const hidden = new Map(); + for (const id of seen) { + const missing = new Set((adj.get(id) ?? []).filter(child => !rendered.get(id)?.has(child))); + if (missing.size) { hidden.set(id, missing.size); } + } + return { nodes: graph.nodes.filter(node => seen.has(node.id)), edges, roots: graph.roots, truncated: graph.truncated, hidden, limited }; +} + +export function mermaidGraph(graph: VisibleGraph): { source: string; nodeIds: string[] } { + const nodeIds = graph.nodes.map(node => node.id), ids = new Map(nodeIds.map((id, index) => [id, `n${index}`])); + const label = (value: string): string => value.replace(/[\r\n]/g, " ").replace(/[&"<>[\]{}|`\\#]/g, char => `#${char.charCodeAt(0)};`); + const source = ["flowchart LR", ...graph.nodes.map(node => { + const hidden = graph.hidden.get(node.id); + const text = [node.label, ...node.lines, ...(hidden ? [`+${hidden}`] : [])].map(label).join("
"); + return ` ${ids.get(node.id)}["${text}"]:::${node.kind}`; + }), ...graph.edges.map(edge => ` ${ids.get(edge.from)} ${edge.dashed ? "-.-" : "--"}>${edge.label ? `|"${label(edge.label)}"|` : ""} ${ids.get(edge.to)}`), " classDef unknown stroke-dasharray: 4 3"].join("\n"); + return { source, nodeIds }; +} diff --git a/src/graphs/panelHtml.ts b/src/graphs/panelHtml.ts new file mode 100644 index 0000000..6b9e34c --- /dev/null +++ b/src/graphs/panelHtml.ts @@ -0,0 +1,31 @@ +import { t } from "../i18n/t"; +import { escapeHtml } from "../util/text"; +import type { WebviewAssets } from "../webview/document"; + +export function graphPanelHtml(assets: WebviewAssets, renderer: string, script: string): string { + const labels = { + copied: t("Copied"), error: t("Could not render the graph. Copy Mermaid source or refresh to retry."), + loading: t("Reading project dependencies…"), empty: t("No module relationships were found for this project."), + limited: t("View limit reached. Focus a node to explore its dependencies."), + partial: t("The source graph is incomplete. Some modules may be omitted."), + expand: t("Expand next level"), selected: t("Selected node"), + }; + const button = (action: string, label: string): string => ``; + return ` + + +

${escapeHtml(t("Dependency graph"))}

+
+
+ + + +
+
${escapeHtml(t("Select a node to explore its relationships."))}
${button("expand", t("Expand next level"))}${button("collapse", t("Collapse"))}${button("focus", t("Focus"))}
+
+
+ +
${escapeHtml(t("Legend and source"))}
${escapeHtml(t("Project"))}path / partitionindex / external${escapeHtml(t("Unavailable"))}

${escapeHtml(t("Packages show declarations; modules show mcppls imports. External modules stop after one hop."))}

${button("manifest", t("Open mcpp.toml"))}
+ + `; +} diff --git a/src/library/addDependency.ts b/src/library/addDependency.ts index d761851..48d7d2b 100644 --- a/src/library/addDependency.ts +++ b/src/library/addDependency.ts @@ -38,6 +38,7 @@ export interface AddDependencyDeps { output?: vscode.OutputChannel; /** `vscode.workspace.isTrusted`; an untrusted workspace may not be written to. */ isTrusted: () => boolean; + quiet?: boolean; } export interface AddDependencyRequest { @@ -82,18 +83,18 @@ export async function addDependency( const version = request.version?.trim() ?? ""; if (request.id.trim().length === 0 || version.length === 0) { const message = t("mcpp add needs an exact version: mcpp add {0}@.", request.id); - void vscode.window.showWarningMessage(message); + if (!deps.quiet) void vscode.window.showWarningMessage(message); return failure(message); } const root = deps.projectRoot(); if (root === undefined) { const message = t("This workspace has no mcpp.toml."); - void vscode.window.showWarningMessage(message); + if (!deps.quiet) void vscode.window.showWarningMessage(message); return failure(message); } if (!deps.isTrusted()) { const message = t("This workspace is not trusted. mcpp commands that write are disabled until you trust it."); - void vscode.window.showWarningMessage(message); + if (!deps.quiet) void vscode.window.showWarningMessage(message); return failure(message); } @@ -128,7 +129,7 @@ export async function addDependency( if (result.exitCode === 0) { const message = t("Added {0} to mcpp.toml.", `${request.id}@${version}`); - void vscode.window.showInformationMessage(message); + if (!deps.quiet) void vscode.window.showInformationMessage(message); return { ok: true, argv, exitCode: 0, message }; } @@ -136,7 +137,7 @@ export async function addDependency( const message = detail.length === 0 ? t("mcpp add failed with exit code {0}.", result.exitCode) - : t("mcpp add failed with exit code {0}: {1}", result.exitCode, detail); - void vscode.window.showErrorMessage(message); + : t("mcpp add failed (exit {0}). See output for details.", result.exitCode); + if (!deps.quiet) void vscode.window.showErrorMessage(message); return { ok: false, argv, exitCode: result.exitCode, message }; } diff --git a/src/library/detailHtml.ts b/src/library/detailHtml.ts index 1c6d292..d061614 100644 --- a/src/library/detailHtml.ts +++ b/src/library/detailHtml.ts @@ -17,6 +17,8 @@ * keyword list and punctuation. It is not a parser and is not meant to be. */ +import { dependencyLocation, type DependencyKind, type ProjectDependencyState } from "./projectState"; + import { BADGE_UI, surfaceLabel, @@ -39,6 +41,7 @@ export interface DetailDependency { version?: string; /** The version the project's `mcpp.lock` resolved, when it is known. */ resolved?: string; + key?: string; } /** One window of real example code. */ @@ -47,6 +50,7 @@ export interface DetailSnippet { startLine: number; lines: string[]; usageLine: number; + fileKey?: string; } /** The outcome of the last `mcpp add`, shown in place. */ @@ -66,6 +70,20 @@ export interface DetailInstalled { export interface DetailModel { /** Everything is already localized by the caller. */ ui: Record; + key?: string; + generation?: number; + projectRoot?: string; + platform?: string; + projectState?: ProjectDependencyState; + dependencyGroups?: Record; + dependencyKind?: DependencyKind; + manifestAvailable?: boolean; + blockedReason?: string; + versionNotice?: string; + loading?: boolean; + preview?: boolean; + previewVersion?: string; + busy?: boolean; id: string; name: string; description?: string; @@ -95,6 +113,7 @@ export interface DetailModel { snippets: DetailSnippet[]; /** The example project the snippets come from, when there is one. */ exampleProject?: string; + exampleProvenance?: string; /** * How a reader brings the package into code (§22): the example project's * real `import …;` / `#include …` lines when there are any, else the honest @@ -123,6 +142,34 @@ export interface DetailAssets { export const DETAIL_UI = { htmlLang: "detail.htmlLang", title: "detail.title", + project: "detail.project", + chooseProject: "detail.chooseProject", + platform: "detail.platform", + version: "detail.version", + otherPlatforms: "detail.otherPlatforms", + declared: "detail.declared", + locked: "detail.locked", + sourceDependency: "detail.sourceDependency", + unknownUsage: "detail.unknownUsage", + usageSource: "detail.usageSource", + source: "detail.source", + loading: "detail.loading", + pending: "detail.pending", + groupChange: "detail.groupChange", + output: "detail.output", + retry: "detail.retry", + openManifest: "detail.openManifest", + openFile: "detail.openFile", + readOnlySource: "detail.readOnlySource", + preview: "detail.preview", + platforms: "detail.platforms", + kind: "detail.kind", + kindNormal: "detail.kind.normal", + kindDev: "detail.kind.dev", + kindBuild: "detail.kind.build", + addDevAction: "detail.addDevAction", + buildHint: "detail.buildHint", + moveHint: "detail.moveHint", overview: "detail.overview", license: "detail.license", repo: "detail.repo", @@ -177,19 +224,15 @@ const BADGE_KEY_UI: Readonly> = { "openkal-platform": DETAIL_UI.badgeOpenkalPlatform, }; -/** - * What the page says to the host. - * - * There is no `ready`: the document is already the whole model, and a page that - * announces its own load only invites the host to render it again — which is - * exactly the reload loop the *library sidebar* shipped with (see the note in - * `libraryHtml.ts`). This host happened to answer `ready` with an empty `return`, - * so the trap never fired here; it is gone all the same. +/** Messages carry a page key and generation at the transport boundary. + * Mounting replays the latest model in place; it never rebuilds the document. */ export type DetailMessage = | { type: "add"; version: string; dev: boolean } | { type: "openUrl"; url: string } - | { type: "copy"; text: string }; + | { type: "copy"; text: string; token?: string } + | { type: "selectProject" | "showOutput" | "retry" | "openManifest" | "mounted" } + | { type: "openDependency" | "openFile"; key: string }; export type UiLabel = (key: string) => string; @@ -237,16 +280,22 @@ function renderLine(line: string, usage: boolean): string { return `${tokens}\n`; } +function actionIcon(kind: "copy" | "source"): string { + const paths = kind === "copy" ? '' : ''; + return ``; +} + function renderSnippets(model: DetailModel, label: UiLabel): string { if (model.snippets.length === 0) { return `

${escapeHtml(label(DETAIL_UI.codeNone))}

`; } - const blocks = model.snippets.map((snippet) => { + const blocks = model.snippets.map((snippet, index) => { const lines = snippet.lines .map((line, index) => renderLine(line, snippet.startLine + index === snippet.usageLine)) .join(""); const caption = fill(label, DETAIL_UI.codeSource, [snippet.file, snippet.startLine]); return [ + `

${escapeHtml(index === 0 ? label(DETAIL_UI.code) : snippet.file.split("/").pop() ?? snippet.file)}

${snippet.fileKey === undefined ? "" : ``}
`, `
${lines}
`, `

${escapeHtml(caption)}

`, ].join("\n"); @@ -254,52 +303,16 @@ function renderSnippets(model: DetailModel, label: UiLabel): string { return blocks.join("\n"); } -/** - * The version matrix, as the page's **selector**: each version is a button that - * aims the command at the top of the page at itself. - * - * A `${escapeHtml(label(DETAIL_UI.addDev))}`, - ...(model.repo === undefined ? [] : [link(model.repo, label(DETAIL_UI.openRepo))]), - ...(model.indexUrl === undefined ? [] : [link(model.indexUrl, label(DETAIL_UI.indexLink))]), - ` `, - // The copy button sits beside the command, not inside it: `command.textContent` - // is what a click copies, and a button inside the paragraph would be copied too. - `
`, - `

${escapeHtml(command)}

`, - ` `, - `
`, - disabled - ? `

${escapeHtml(label(DETAIL_UI.addNoVersion))}

` - : `

${escapeHtml(label(DETAIL_UI.addLatest))}

`, - ...usage, - `

${escapeHtml(model.result?.message ?? "")}

`, - ``, - ] - .filter((line) => line.length > 0) - .join("\n"); + const kind = model.dependencyKind ?? (model.projectState?.dev || model.installed?.dev ? "dev" : "normal"); + const groups = groupsFor(model); + const current = groups[kind]; + const version = selectedVersion(model); + const manual = kind === "build" || current.ambiguous || current.declared && current.sourceType !== "registry" || !current.declared && groups[kind === "dev" ? "normal" : "dev"].declared; + const already = current.declaredVersion !== undefined && current.declaredVersion === version && !manual; + const canOpen = model.manifestAvailable ?? !!model.projectRoot; + const disabled = !!model.busy || !!model.loading || (manual ? !canOpen : !!model.blockedReason || !version || already); + const location = dependencyLocation(model.id, kind); + const command = model.preview ? `mcpp search ${model.id}` : manual ? `[${location.table}] +${location.name} = ${JSON.stringify(version ?? "")}` : (kind === "dev" ? model.commandDevTemplate : model.commandTemplate).replace("{0}", model.id).replace("{1}", version ?? "?"); + const addLabel = model.busy ? label(DETAIL_UI.pending) : manual ? label(DETAIL_UI.openManifest) : already ? label(DETAIL_UI.alreadyAdded) : current.declared ? fill(label, DETAIL_UI.switchTo, [version ?? "?"]) : label(kind === "dev" ? DETAIL_UI.addDevAction : DETAIL_UI.add); + const link = (url: string, text: string) => ``; + const kinds: Array<[DependencyKind, string, string]> = [["normal", DETAIL_UI.kindNormal, "dependencies"], ["dev", DETAIL_UI.kindDev, "dev-dependencies"], ["build", DETAIL_UI.kindBuild, "build-dependencies"]]; + return `
+ ${model.loading ? `

${escapeHtml(label(DETAIL_UI.loading))}

` : ""} + ${model.preview ? `

${escapeHtml(label(DETAIL_UI.preview))}

${escapeHtml(label(DETAIL_UI.version))}: ${escapeHtml(model.previewVersion ?? "—")}

` : ""} + ${model.preview ? "" : `
${kind === "dev" ? "dev-dependencies" : kind === "build" ? "build-dependencies" : "dependencies"}${current.resolvedVersion && current.resolvedVersion !== current.declaredVersion ? `${escapeHtml(label(DETAIL_UI.locked))}: ${escapeHtml(current.resolvedVersion)}` : ""}
`} +
+ ${model.preview ? "" : `
`} + ${model.repo === undefined ? "" : link(model.repo, label(DETAIL_UI.openRepo))} + ${model.indexUrl === undefined ? "" : link(model.indexUrl, label(DETAIL_UI.indexLink))} +
+ ${model.versionNotice ? `

${escapeHtml(model.versionNotice)}

` : ""} + ${model.blockedReason ? `

${escapeHtml(model.blockedReason)}

` : ""} +

${escapeHtml(manual ? label(kind === "build" ? DETAIL_UI.buildHint : DETAIL_UI.moveHint) : "")}

+

${escapeHtml(command)}

+

${escapeHtml(model.result?.message ?? "")}

+
+
`; +} + +export function detailClientState(model: DetailModel): Record { + return { + key: model.key ?? model.id, generation: model.generation ?? 0, projectRoot: model.projectRoot, + id: model.id, versions: model.currentVersions, version: selectedVersion(model), + groups: groupsFor(model), kind: model.dependencyKind ?? (model.projectState?.dev || model.installed?.dev ? "dev" : "normal"), + locations: Object.fromEntries((["normal", "dev", "build"] as const).map((kind) => [kind, dependencyLocation(model.id, kind)])), + loading: !!model.loading, canOpenManifest: model.manifestAvailable ?? !!model.projectRoot, + blocked: !!model.blockedReason, busy: !!model.busy, preview: !!model.preview, + command: model.commandTemplate, commandDev: model.commandDevTemplate, + labels: Object.fromEntries(Object.entries(DETAIL_UI).map(([name, key]) => [name, model.ui[key] ?? key])), + }; } -/** - * The client. It posts two things — `add` and `openUrl` — and applies - * the host's `{type:"result"}` in place, so running `mcpp add` never rebuilds the - * document and never resets the version the reader picked. - */ function clientScript(initialModel: string): string { return `(function () { "use strict"; var api = typeof acquireVsCodeApi === "function" ? acquireVsCodeApi() : undefined; var state = ${initialModel}; - var devInput = document.getElementById("detail-dev"); - var command = document.getElementById("detail-command"); - var result = document.getElementById("detail-result"); - var addButton = document.getElementById("detail-add"); - var version = command ? (command.getAttribute("data-selected-version") || "") : ""; - var labels = (state && state.labels) || {}; - var installed = state && state.installed ? state.installed : ""; - - function post(message) { - if (api) { api.postMessage(message); } + var saved = api && api.getState ? api.getState() || {} : {}; + var pages = saved.pages || {}; + var busy = state.busy; + var version = state.version || ""; + var kind = state.kind || "normal"; + var copyId = 0; + var copyRequests = {}; + function copyLabel(button, text) { (button.querySelector("[data-action-label]") || button).textContent = text; } + function restoreCopyFeedback() { + Object.keys(copyRequests).forEach(function (token) { + var request = copyRequests[token]; + if (request.key !== pageKey() || request.until < Date.now()) { delete copyRequests[token]; return; } + var buttons = Array.from(document.querySelectorAll(request.command ? "[data-copy-command]" : "[data-copy]")); + var button = buttons.find(function (node) { return request.command ? document.getElementById("detail-command").textContent === request.text : node.getAttribute("data-copy") === request.text; }); + if (button) { button.setAttribute("data-copy-token", token); if (request.message) { copyLabel(button, request.message); } } + }); } - - function fillTemplate(template, values) { - var out = template; - for (var index = 0; index < values.length; index += 1) { - out = out.split("{" + index + "}").join(String(values[index])); - } - return out; + function pageKey() { return state.key + "|" + (state.projectRoot || ""); } + function post(message) { message.pageKey = state.key; message.generation = state.generation; if (api) { api.postMessage(message); } } + function persist() { + if (state.loading) { return; } + var expanded = Array.from(document.querySelectorAll("details[data-persist]")).filter(function (node) { return node.open; }).map(function (node) { return node.getAttribute("data-persist"); }); + pages[pageKey()] = { version: version, kind: kind, scroll: window.scrollY, expanded: expanded }; + var keys = Object.keys(pages); while (keys.length > 20) { delete pages[keys.shift()]; } + if (api && api.setState) { api.setState({ pages: pages }); } } - - function updateCommand() { - if (!command) { return; } - var dev = devInput ? devInput.checked === true : false; - var template = dev - ? (command.getAttribute("data-template-dev") || "") - : (command.getAttribute("data-template") || ""); - command.setAttribute("data-selected-version", version); - command.textContent = fillTemplate(template, [state.id, version || "?"]); + function closeKindMenu(focus) { + var menu = document.getElementById("detail-kind-menu"); + var toggle = document.getElementById("detail-kind-toggle"); + if (menu) { menu.hidden = true; } + if (toggle) { toggle.setAttribute("aria-expanded", "false"); if (focus) { toggle.focus(); } } } - - /** - * The button's label and enabled state follow the selection: adding a new - * package, switching the version of one that is already there, or nothing to do - * because this is the version the manifest already asks for. The inert state - * carries the data-installed attribute, which is what paints it the thinned - * green of a thing already done. - */ - function updateButton() { - if (!addButton) { return; } - if (!version) { - addButton.textContent = labels.add || ""; - addButton.disabled = true; - addButton.removeAttribute("data-installed"); - return; - } - if (installed && version === installed) { - addButton.textContent = labels.alreadyAdded || ""; - addButton.disabled = true; - addButton.setAttribute("data-installed", ""); - return; - } - addButton.textContent = installed - ? (labels.switchTo || "{0}").split("{0}").join(version) - : (labels.add || ""); - addButton.disabled = false; - addButton.removeAttribute("data-installed"); + function manualSelection() { + var group = state.groups[kind]; + return kind === "build" || group.ambiguous || group.declared && group.sourceType !== "registry" || !group.declared && state.groups[kind === "dev" ? "normal" : "dev"].declared; } - - /** One version button is the selection; the rest are alternatives. */ - function selectVersion(next) { - version = next; - var buttons = document.querySelectorAll("[data-version]"); - for (var index = 0; index < buttons.length; index += 1) { - var button = buttons[index]; - if (button.getAttribute("data-version") === next) { - button.setAttribute("data-selected", ""); - } else { - button.removeAttribute("data-selected"); - } + function update() { + var select = document.getElementById("detail-version"); + var button = document.getElementById("detail-add"); + if (state.preview) { return; } + var group = state.groups[kind]; + var manual = manualSelection(); + var same = !!group.declaredVersion && group.declaredVersion === version && !manual; + if (select) { select.value = version; select.disabled = busy || state.loading || !state.versions.length; } + var toggle = document.getElementById("detail-kind-toggle"); if (toggle) { toggle.disabled = busy || state.loading; } + var kindLabel = document.getElementById("detail-kind-label"); + if (kindLabel) { kindLabel.textContent = kind === "dev" ? "dev-dependencies" : kind === "build" ? "build-dependencies" : "dependencies"; } + document.querySelectorAll("[data-kind]").forEach(function (item) { item.setAttribute("aria-checked", String(item.getAttribute("data-kind") === kind)); }); + if (button) { + button.disabled = busy || state.loading || (manual ? !state.canOpenManifest : state.blocked || !version || same); + button.textContent = busy ? state.labels.pending : manual ? state.labels.openManifest : same ? state.labels.alreadyAdded : group.declared ? state.labels.switchTo.split("{0}").join(version) : kind === "dev" ? state.labels.addDevAction : state.labels.add; + if (same) { button.setAttribute("data-installed", ""); } else { button.removeAttribute("data-installed"); } } - updateCommand(); - updateButton(); + var note = document.getElementById("detail-selection-note"); if (note) { note.textContent = manual ? kind === "build" ? state.labels.buildHint : state.labels.moveHint : ""; note.hidden = !manual; } + var command = document.getElementById("detail-command"); + var location = state.locations[kind]; + if (command) { command.textContent = manual ? "[" + location.table + "]\\n" + location.name + " = " + JSON.stringify(version || "") : (kind === "dev" ? state.commandDev : state.command).split("{0}").join(state.id).split("{1}").join(version || "?"); } } - - function showResult(payload) { - if (!result) { return; } - result.hidden = false; - result.setAttribute( - "data-state", - payload.state === "ok" ? "ok" : payload.state === "pending" ? "pending" : "error", - ); - result.textContent = payload.message || ""; + function restore() { + var page = pages[pageKey()] || {}; + if (state.versions.indexOf(page.version) >= 0) { version = page.version; } + kind = ["normal", "dev", "build"].indexOf(page.kind) >= 0 ? page.kind : state.kind || "normal"; + document.querySelectorAll("details[data-persist]").forEach(function (node) { node.open = (page.expanded || []).indexOf(node.getAttribute("data-persist")) >= 0; }); + update(); window.scrollTo(0, page.scroll || 0); } - - if (devInput) { devInput.addEventListener("change", updateCommand); } - + document.addEventListener("change", function (event) { + if (event.target.id === "detail-version") { version = event.target.value; } + if (event.target.id === "detail-version") { update(); persist(); } + }); + document.addEventListener("toggle", persist, true); + window.addEventListener("scroll", persist); document.addEventListener("click", function (event) { - var target = event.target; - if (!target || typeof target.closest !== "function") { return; } - var link = target.closest("[data-open-url]"); - if (link) { - event.preventDefault(); - var url = link.getAttribute("data-open-url") || ""; - // Say something the instant the click lands: the host's answer replaces - // this line, and if it never arrives the reader can see that too. - showResult({ state: "pending", message: (labels.opening || "{0}").split("{0}").join(url) }); - post({ type: "openUrl", url: url }); + var target = event.target.closest && event.target.closest("button"); + if (!target || target.disabled) { return; } + if (target.id === "detail-kind-toggle") { + var menu = document.getElementById("detail-kind-menu"); menu.hidden = !menu.hidden; + target.setAttribute("aria-expanded", String(!menu.hidden)); + if (!menu.hidden) { menu.querySelector('[aria-checked="true"]').focus(); } return; } - var copy = target.closest("[data-copy-command], [data-copy]"); - if (copy) { - // The command's own text is what gets copied — the button beside it, not - // inside it, is why the command's textContent is exactly the command. - var text = copy.hasAttribute("data-copy-command") - ? (command ? String(command.textContent || "") : "") - : String(copy.getAttribute("data-copy") || ""); - if (!text) { return; } - showResult({ state: "pending", message: labels.copying || "" }); - post({ type: "copy", text: text }); - return; + if (target.hasAttribute("data-kind")) { + kind = target.getAttribute("data-kind"); + var declared = state.groups[kind].declaredVersion; + if (state.versions.indexOf(declared) >= 0) { version = declared; } + closeKindMenu(true); update(); persist(); return; } - var pick = target.closest("[data-version]"); - if (pick) { - selectVersion(pick.getAttribute("data-version") || ""); - return; + closeKindMenu(false); + var action = target.getAttribute("data-action"); + if (action) { post({ type: action }); return; } + if (target.hasAttribute("data-open-dependency")) { persist(); post({ type: "openDependency", key: target.getAttribute("data-open-dependency") }); return; } + if (target.hasAttribute("data-open-file")) { post({ type: "openFile", key: target.getAttribute("data-open-file") }); return; } + if (target.hasAttribute("data-open-url")) { post({ type: "openUrl", url: target.getAttribute("data-open-url") }); return; } + if (target.hasAttribute("data-copy") || target.hasAttribute("data-copy-command")) { + var token = "copy-" + (++copyId); target.setAttribute("data-copy-token", token); + copyRequests[token] = { key: pageKey(), command: target.hasAttribute("data-copy-command"), text: target.hasAttribute("data-copy") ? target.getAttribute("data-copy") : document.getElementById("detail-command").textContent, until: Date.now() + 20000 }; + post({ type: "copy", text: target.hasAttribute("data-copy") ? target.getAttribute("data-copy") : document.getElementById("detail-command").textContent, token: token }); return; } - var add = target.closest("#detail-add"); - if (add && !add.disabled) { + if (target.id === "detail-add" && !busy) { + if (manualSelection()) { post({ type: "openManifest" }); return; } if (!version) { return; } - post({ type: "add", version: version, dev: devInput ? devInput.checked === true : false }); + busy = true; update(); + var result = document.getElementById("detail-result"); result.hidden = false; result.dataset.state = "pending"; result.textContent = state.labels.pending; + persist(); post({ type: "add", version: version, dev: kind === "dev" }); } }); - - /** Move the "added" marker onto the version the project now asks for. */ - function markInstalled(next) { - installed = next; - var marks = document.querySelectorAll(".detail-installed"); - for (var index = 0; index < marks.length; index += 1) { marks[index].parentNode.removeChild(marks[index]); } - var buttons = document.querySelectorAll("[data-version]"); - for (var index2 = 0; index2 < buttons.length; index2 += 1) { - var button = buttons[index2]; - if (button.getAttribute("data-version") !== next) { - button.removeAttribute("data-installed"); - continue; - } - button.setAttribute("data-installed", ""); - var mark = document.createElement("span"); - mark.className = "detail-installed"; - mark.textContent = labels.installed || ""; - button.parentNode.insertBefore(mark, button.nextSibling); - } - updateButton(); - } - + document.addEventListener("click", function (event) { if (!event.target.closest || !event.target.closest(".detail-add-group")) { closeKindMenu(false); } }); + document.addEventListener("keydown", function (event) { + var menu = document.getElementById("detail-kind-menu"); + if (!menu || menu.hidden) { return; } + if (event.key === "Escape") { event.preventDefault(); closeKindMenu(true); return; } + if (event.key === "Tab") { closeKindMenu(false); return; } + var options = Array.from(menu.querySelectorAll("[data-kind]")); + if (["ArrowDown", "ArrowUp", "Home", "End"].indexOf(event.key) < 0) { return; } + event.preventDefault(); var index = options.indexOf(document.activeElement); + index = event.key === "Home" ? 0 : event.key === "End" ? options.length - 1 : (index + (event.key === "ArrowDown" ? 1 : options.length - 1)) % options.length; + options[index].focus(); + }); window.addEventListener("message", function (event) { var data = event.data; - if (data && data.type === "result" && data.result) { showResult(data.result); } - if (data && data.added && data.added.version) { markInstalled(data.added.version); } + if (!data) { return; } + if (data.type === "detailUpdate") { + persist(); + var focused = document.activeElement && document.activeElement.id; + var doc = new DOMParser().parseFromString(data.html, "text/html"); + ["detail-header", "detail-main", "detail-footer"].forEach(function (id) { var old = document.getElementById(id); var next = doc.getElementById(id); if (old && next) { old.replaceWith(next); } }); + state = data.state; busy = state.busy; version = state.version || ""; kind = state.kind || "normal"; + restore(); restoreCopyFeedback(); + var nextFocus = focused && document.getElementById(focused); if (nextFocus) { nextFocus.focus({ preventScroll: true }); } + return; + } + if (data.key !== state.key || data.generation !== state.generation) { return; } + if (data.type === "copyResult") { + var request = copyRequests[data.token]; + if (request) { + request.message = data.message; request.until = Date.now() + 1800; restoreCopyFeedback(); + setTimeout(function () { var button = document.querySelector('[data-copy-token="' + data.token + '"]'); if (button) { copyLabel(button, state.labels.copy); } delete copyRequests[data.token]; }, 1800); + } + } + if (data.type === "linkResult") { var status = document.getElementById("detail-link-result"); status.textContent = data.message; status.hidden = !data.message; } }); - - updateCommand(); - updateButton(); + restore(); + post({ type: "mounted" }); })();`; } -/** The little state the client needs; the page is already rendered from the rest. */ -function clientState(model: DetailModel): Record { - return { - id: model.id, - ...(model.latest === undefined ? {} : { latest: model.latest }), - // The three labels the button can wear, and the version the project already - // has: the client re-decides the label every time the selection changes, so - // the host cannot be the only one that knows what the button means. The - // `installed` word belongs here too — `markInstalled()` writes it next to - // the version button after a successful add, and an empty string there is - // a marker nobody can read (§22). - labels: { - add: model.ui[DETAIL_UI.add] ?? "", - switchTo: model.ui[DETAIL_UI.switchTo] ?? "", - alreadyAdded: model.ui[DETAIL_UI.alreadyAdded] ?? "", - opening: model.ui[DETAIL_UI.opening] ?? "", - installed: model.ui[DETAIL_UI.installed] ?? "", - copying: model.ui[DETAIL_UI.copying] ?? "", - }, - ...(model.installed === undefined ? {} : { installed: model.installed.version }), - }; -} - /** A model embedded in the page's own script; `<` is escaped so it cannot close it. */ function embedJson(value: unknown): string { return JSON.stringify(value) @@ -637,35 +605,31 @@ export function renderDetailHtml(model: DetailModel, assets: DetailAssets): stri ${escapeHtml(title)} -
-

${escapeHtml(model.id)}

+
+

${escapeHtml(model.id)}

${renderMeta(model, label)}
${renderProject(model, label)}
${model.description === undefined ? "" : `

${escapeHtml(model.description)}

`} - ${renderMeta(model, label)}
-
+
${renderActions(model, label)} -
-

${escapeHtml(versionsHeading)}

- ${renderVersions(model, label)} -
-
-

${escapeHtml(label(DETAIL_UI.dependencies))}

- ${renderDependencies(model, label)} -
-
-

${escapeHtml(label(DETAIL_UI.code))}

- ${renderSnippets(model, label)} - ${model.exampleProject === undefined ? "" : `

${escapeHtml(fill(label, DETAIL_UI.codeProject, [model.exampleProject]))}

`} -
-
-

${escapeHtml(label(DETAIL_UI.overview))}

- ${renderExtras(model, label)} - ${model.parseNotice === undefined ? "" : `

${escapeHtml(model.parseNotice)}

`} -
+ + ${model.preview ? "" : `
+
+

${escapeHtml(label(DETAIL_UI.usage))}

+ ${model.usage.length ? `

${escapeHtml(label(DETAIL_UI.usageSource))}

` + model.usage.map((line) => `
${escapeHtml(line)}
`).join("") : model.snippets.length ? "" : `

${escapeHtml(label(DETAIL_UI.unknownUsage))}

`} + ${model.snippets.length ? renderSnippets(model, label) : ""} + ${model.exampleProject === undefined ? "" : `

${escapeHtml(fill(label, DETAIL_UI.codeProject, [model.exampleProject]))}

`} + ${model.exampleProvenance === undefined ? "" : `

${escapeHtml(model.exampleProvenance)}

`} +
+ +
`}
-
${escapeHtml(model.dataSource)}
+
${escapeHtml(label(DETAIL_UI.source))}

${escapeHtml(model.dataSource)}

${model.projectRoot ? `` : ""}

${escapeHtml(badgeLabels(model.badges, label).join(" · "))}

${model.parseNotice === undefined ? "" : `

${escapeHtml(model.parseNotice)}

`}
@@ -691,6 +655,15 @@ export function decodeDetailMessage(raw: unknown): DetailMessage | undefined { return undefined; } switch (raw.type) { + case "mounted": + case "selectProject": + case "showOutput": + case "retry": + case "openManifest": + return { type: raw.type }; + case "openDependency": + case "openFile": + return nonEmptyString(raw.key) ? { type: raw.type, key: raw.key } : undefined; case "add": return nonEmptyString(raw.version) && typeof raw.dev === "boolean" ? { type: "add", version: raw.version, dev: raw.dev } @@ -705,7 +678,7 @@ export function decodeDetailMessage(raw: unknown): DetailMessage | undefined { // hostile document cannot park a novel in the clipboard — and everything // else about the text is the user's own click. return nonEmptyString(raw.text) && raw.text.length <= 16_384 - ? { type: "copy", text: raw.text } + ? { type: "copy", text: raw.text, ...(typeof raw.token === "string" && /^copy-\d+$/.test(raw.token) ? { token: raw.token } : {}) } : undefined; default: return undefined; diff --git a/src/library/detailPanel.ts b/src/library/detailPanel.ts index b784b26..2fb0adb 100644 --- a/src/library/detailPanel.ts +++ b/src/library/detailPanel.ts @@ -18,7 +18,7 @@ * file. */ -import { readFileSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import * as path from "node:path"; import * as vscode from "vscode"; @@ -27,28 +27,28 @@ import { read } from "../config/access"; import { languagePreference, t } from "../i18n/t"; import { localeFromEditorLanguage } from "../i18n/translate"; import { WebviewDocument } from "../webview/document"; +import { projectDependencyState, readProjectDeclarations, operationMatches } from "./projectState"; +import type { LibraryRow } from "./libraryHtml"; import { addDependency } from "./addDependency"; import { badgesOf, - codeSnippets, descriptorDependencies, - installedFor, lockPackageVersions, mergeSurfaces, parseXpkgJson, platformKey, surfaceLabel, - syntheticUsageLines, usageLinesFor, type LibraryEntry, type Surface, } from "./indexModel"; import { cachedDescriptorText, loadSnapshot, readDescriptorText, readExampleFiles } from "./indexLocator"; +import { bundledExample, ExampleDocuments, EXAMPLE_SCHEME } from "./examples"; import { DETAIL_UI, decodeDetailMessage, renderDetailHtml, - type DetailInstalled, + detailClientState, type DetailModel, type DetailVersionGroup, type DetailResult, @@ -63,7 +63,9 @@ const STYLESHEET = "library.css"; const PARSE_TIMEOUT_MS = 20_000; export interface DetailPanelDeps { - mcppExecutable: () => string; + mcppExecutable: (projectRoot?: string) => string; + chooseProject?: () => Promise; + onPackageOpened?: (key: string) => void; projectRoot: () => string | undefined; output: vscode.OutputChannel; isTrusted: () => boolean; @@ -74,19 +76,30 @@ export interface DetailPanelDeps { * library view's `refresh()`. */ onDependenciesChanged?: () => void; + onProjectFilesRead?: (root: string, manifest: string, lock: string) => void; } /** Build the opener once; `extension.ts` passes `openLibraryDetail` to the view. */ export function createLibraryDetailOpener( context: vscode.ExtensionContext, deps: DetailPanelDeps, -): (id: string) => Promise { +): LibraryDetailOpener { + const sourceChanged = new vscode.EventEmitter(); + const exampleDocuments = new ExampleDocuments((uri) => sourceChanged.fire(vscode.Uri.parse(uri))); + context.subscriptions.push(sourceChanged, vscode.workspace.registerTextDocumentContentProvider(EXAMPLE_SCHEME, { + onDidChange: sourceChanged.event, + provideTextDocumentContent: (uri) => exampleDocuments.read(uri.toString()) ?? "", + })); const session: DetailSession = { context, + exampleDocuments, deps, panel: undefined, id: undefined, busy: false, + generation: 0, + projectRoot: deps.projectRoot(), + painted: false, webviewDocument: new WebviewDocument(STYLESHEET), }; context.subscriptions.push({ @@ -95,17 +108,41 @@ export function createLibraryDetailOpener( session.panel = undefined; }, }); - return (id: string) => open(session, id); + const opener = (id: string, preview?: LibraryRow) => open(session, id, preview); + opener.refresh = async () => { + if (session.id === undefined || session.panel === undefined) { return; } + const root = deps.projectRoot(); + if (root !== session.projectRoot) { + session.projectRoot = root; session.generation++; session.result = undefined; + if (session.model) { paintModel(session, { ...session.model, generation: session.generation, projectRoot: root, projectState: undefined, installed: undefined, loading: true, blockedReason: t("Reading package details…"), result: undefined }); } + } + await render(session, session.id); + }; + return opener; +} + +export interface LibraryDetailOpener { + (id: string, preview?: LibraryRow): Promise; + refresh(): Promise; } /** One panel per window, remembered so a second `open` reveals rather than stacks. */ export interface DetailSession { + exampleDocuments: ExampleDocuments; context: vscode.ExtensionContext; deps: DetailPanelDeps; panel: vscode.WebviewPanel | undefined; id: string | undefined; /** `mcpp add` in flight: a second click must not start a second command. */ busy: boolean; + generation: number; + renderRequest?: number; + projectRoot?: string; + preview?: LibraryRow; + model?: DetailModel; + painted: boolean; + lastHtml?: string; + result?: DetailResult; /** * The document and its nonce, kept per session so re-opening the *same* * package renders byte for byte the same document and does not reload the @@ -117,7 +154,7 @@ export interface DetailSession { /** The parse result per descriptor, so re-opening a package costs no process. */ const parsed = new Map(); -export async function open(session: DetailSession, id: string): Promise { +export async function open(session: DetailSession, id: string, preview?: LibraryRow): Promise { if (session.panel === undefined) { const panel = vscode.window.createWebviewPanel( PANEL_VIEW_TYPE, @@ -133,6 +170,9 @@ export async function open(session: DetailSession, id: string): Promise { panel.onDidDispose(() => { session.panel = undefined; session.id = undefined; + session.generation++; + session.painted = false; + session.lastHtml = undefined; // The next open creates a brand-new webview: what was on screen says // nothing about it, so the document comparison starts from nothing. session.webviewDocument.invalidate(); @@ -142,161 +182,198 @@ export async function open(session: DetailSession, id: string): Promise { }); } const panel = session.panel; + const changed = session.id !== id; session.id = id; - panel.title = id; + session.preview = preview; + session.deps.onPackageOpened?.(id); + if (changed) { session.generation++; session.result = undefined; } + const nextProject = session.deps.projectRoot(); + if (nextProject !== session.projectRoot) { session.generation++; session.result = undefined; } + session.projectRoot = nextProject; + panel.title = preview?.id ?? path.basename(id, ".lua"); + if (changed || !session.painted) { + const loading = emptyModel(preview?.id ?? path.basename(id, ".lua"), labels(), t("Reading package details…")); + loading.key = id; loading.generation = session.generation; loading.projectRoot = session.projectRoot; + loading.loading = true; loading.blockedReason = t("Reading package details…"); + paintModel(session, loading); + } panel.reveal(vscode.ViewColumn.Active, false); await render(session, id); } async function handle(session: DetailSession, raw: unknown): Promise { const message = decodeDetailMessage(raw); - if (message === undefined || session.panel === undefined) { + if (message === undefined || session.panel === undefined || session.id === undefined) { return; } + const envelope = raw as { pageKey?: unknown; generation?: unknown }; + if (message.type === "mounted") { + if (session.model) { paintModel(session, session.model, true); } return; } + if (envelope.pageKey !== session.id || envelope.generation !== session.generation) { return; } + const operation = { key: session.id, projectRoot: session.projectRoot, generation: session.generation }; + const current = () => operationMatches(operation, { key: session.id ?? "", projectRoot: session.projectRoot, generation: session.generation }); switch (message.type) { - case "openUrl": { - // `decodeDetailMessage` already restricted this to https. - await openExternal(session, message.url); + case "selectProject": { + const root = await session.deps.chooseProject?.(); + if (root !== undefined && current()) { + session.projectRoot = root; session.generation++; session.result = undefined; + if (session.model) { paintModel(session, { ...session.model, generation: session.generation, projectRoot: root, projectState: undefined, installed: undefined, loading: true, blockedReason: t("Reading package details…"), result: undefined }); } + await render(session, session.id); + } + return; + } + case "showOutput": session.deps.output.show(true); return; + case "openManifest": + if (session.projectRoot) { await vscode.window.showTextDocument(vscode.Uri.file(path.join(session.projectRoot, "mcpp.toml"))); } + return; + case "retry": + if (session.result?.state === "error") { session.result = undefined; } + await render(session, session.id); return; + case "openDependency": + if (session.model?.dependencies.some((dependency) => dependency.key === message.key)) { await open(session, message.key); } + return; + case "openFile": { + if (!session.model?.snippets.some((snippet) => snippet.fileKey === message.key)) { return; } + if (message.key.startsWith(`${EXAMPLE_SCHEME}:`)) { await vscode.window.showTextDocument(vscode.Uri.parse(message.key)); } return; } case "copy": { - // The command preview and the usage lines: both are text a reader pastes - // somewhere else, and a click that answers with nothing looks like a dead - // button (the lesson `openExternal` learned in §20.2). - try { - await vscode.env.clipboard.writeText(message.text); - postResult(session, { state: "ok", message: t("Copied to the clipboard.") }); - } catch (error) { - session.deps.output.appendLine( - `mcpp library: copying to the clipboard failed: ${error instanceof Error ? error.message : String(error)}`, - ); - postResult(session, { state: "error", message: t("Could not write the clipboard.") }); - } + let result: string; + try { await vscode.env.clipboard.writeText(message.text); result = t("Copied"); } + catch { result = t("Copy failed"); } + if (current()) { void session.panel?.webview.postMessage({ type: "copyResult", key: operation.key, generation: operation.generation, token: message.token, message: result }); } + return; + } + case "openUrl": { + // Only links in this package's model can ask the host to open a browser. + if (message.url !== session.model?.repo && message.url !== session.model?.indexUrl) { return; } + let opened = false; + try { opened = await vscode.env.openExternal(vscode.Uri.parse(message.url)); } catch { /* report below */ } + let copied = false; + if (!opened) { try { await vscode.env.clipboard.writeText(message.url); copied = true; } catch { /* accurate feedback below */ } } + if (current()) { void session.panel?.webview.postMessage({ type: "linkResult", key: operation.key, generation: operation.generation, + message: opened ? t("Opened in your browser.") : copied ? t("Could not open the browser. Link copied to clipboard.") : t("Could not open the browser or copy the link: {0}", message.url) }); } return; } case "add": { - if (session.busy || session.id === undefined) { - return; - } + const model = session.model; + if (session.busy || !model || model.blockedReason || model.preview || !model.currentVersions.includes(message.version)) { return; } + const state = model.dependencyGroups?.[message.dev ? "dev" : "normal"] ?? model.projectState; + const other = model.dependencyGroups?.[message.dev ? "normal" : "dev"]; + if (state?.ambiguous || state?.declared && state.sourceType !== "registry" || !state?.declared && other?.declared) { return; } session.busy = true; - let result: DetailResult; + session.result = { state: "pending", message: t("Adding dependency…") }; + paintModel(session, { ...model, busy: true, result: session.result }); try { - const outcome = await addDependency( - { - mcppExecutable: session.deps.mcppExecutable, - projectRoot: session.deps.projectRoot, - output: session.deps.output, - isTrusted: session.deps.isTrusted, - }, - { id: session.id, version: message.version, dev: message.dev }, - ); - result = { state: outcome.ok ? "ok" : "error", message: outcome.message }; - if (outcome.ok) { - session.deps.onDependenciesChanged?.(); + const executable = session.deps.mcppExecutable(operation.projectRoot); + const snapshot = await loadSnapshot({ projectRoot: operation.projectRoot }); + const declarations = readProjectDeclarations(operation.projectRoot ? readTextTolerantly(path.join(operation.projectRoot, "mcpp.toml")) : ""); + const catalog = snapshot.entries.map((entry) => entry.id); + const fresh = projectDependencyState(model.id, declarations, [], catalog, message.dev ? "dev" : "normal"); + const otherGroup = projectDependencyState(model.id, declarations, [], catalog, message.dev ? "normal" : "dev"); + if (fresh.ambiguous || fresh.declared && fresh.sourceType !== "registry" || !fresh.declared && otherGroup.declared) { + if (current()) { session.result = { state: "error", message: t("The dependency declaration changed. Review mcpp.toml before adding it.") }; } + return; + } + const outcome = await addDependency({ + mcppExecutable: () => executable, + projectRoot: () => operation.projectRoot, + isTrusted: session.deps.isTrusted, + output: session.deps.output, + quiet: true, + }, { id: model.id, version: message.version, dev: message.dev }); + if (outcome.ok) { session.deps.onDependenciesChanged?.(); } + if (current() && session.panel) { + session.result = { state: outcome.ok ? "ok" : "error", message: outcome.message }; + } else { + void (outcome.ok ? vscode.window.showInformationMessage(outcome.message) : vscode.window.showErrorMessage(outcome.message)); } + } catch (error) { + session.deps.output.appendLine(String(error)); + if (current()) { session.result = { state: "error", message: t("Could not add the dependency. See output for details.") }; } } finally { session.busy = false; + if (session.panel && session.id) { await render(session, session.id); } } - if (session.panel === undefined) { - return; - } - postResult(session, result, result.state === "ok" ? message.version : undefined); return; } - default: - return; } } -/** - * Open a link, and say what happened. - * - * This used to be `void vscode.env.openExternal(…)`: the boolean it resolves to - * was dropped, so a host with no browser (or no way to reach one) answered a - * click with nothing at all — indistinguishable from a dead button. Now the page - * is told either way, a failure is copied to the clipboard so the click is still - * worth something, and the output channel keeps a record. - */ -async function openExternal(session: DetailSession, url: string): Promise { - let opened = false; - try { - opened = await vscode.env.openExternal(vscode.Uri.parse(url)); - } catch (error) { - session.deps.output?.appendLine( - `mcpp library: opening ${url} failed: ${error instanceof Error ? error.message : String(error)}`, - ); - } - if (opened) { - postResult(session, { state: "ok", message: t("Opened {0} in your browser.", url) }); - return; - } - session.deps.output?.appendLine(`mcpp library: VS Code could not open ${url}; copying it instead.`); - try { - await vscode.env.clipboard.writeText(url); - } catch { - // A clipboard that refuses is not worth a second error. +function paintModel(session: DetailSession, model: DetailModel, replay = false): void { + const panel = session.panel; + if (!panel) { return; } + session.model = model; + const html = renderDetailHtml(model, session.webviewDocument.assets(vscode.Uri.joinPath(session.context.extensionUri, MEDIA_DIRECTORY), panel.webview)); + if (session.lastHtml === html && !replay) { return; } + session.lastHtml = html; + if (!session.painted && !replay) { + session.webviewDocument.paint(panel.webview, html); session.painted = true; + } else { + void panel.webview.postMessage({ type: "detailUpdate", html, state: detailClientState(model) }); } - postResult(session, { - state: "error", - message: t("VS Code could not open {0}. The link is on your clipboard.", url), - }); -} - -/** - * The page's status line, in the same shape the add flow uses. - * - * `added` tells the page that the project now depends on that version, so it can - * move its "added" marker and re-label the button without rebuilding the - * document — which would throw away the reader's scroll position and their - * version selection, the one thing running `mcpp add` must not do. - */ -function postResult(session: DetailSession, result: DetailResult, added?: string): void { - void session.panel?.webview.postMessage({ - type: "result", - result, - ...(added === undefined ? {} : { added: { version: added } }), - }); } async function render(session: DetailSession, id: string): Promise { const panel = session.panel; - if (panel === undefined) { - return; - } - const model = await buildModel(session, id); - if (session.panel !== panel || session.id !== id) { - return; + const generation = session.generation; + const request = session.renderRequest = (session.renderRequest ?? 0) + 1; + const root = session.projectRoot; + if (!panel) { return; } + let model: DetailModel; + try { model = await buildModel(session, id, request); } + catch (error) { + session.deps.output.appendLine(String(error)); + model = emptyModel(path.basename(id, ".lua"), labels(), t("Could not read package details. See output for details.")); + model.blockedReason = model.parseNotice; + model.result = { state: "error", message: model.parseNotice ?? "" }; } - const html = renderDetailHtml( - model, - session.webviewDocument.assets( - vscode.Uri.joinPath(session.context.extensionUri, MEDIA_DIRECTORY), - panel.webview, - ), - ); - session.webviewDocument.paint(panel.webview, html); + if (session.panel !== panel || session.id !== id || generation !== session.generation || request !== session.renderRequest || root !== session.projectRoot) { return; } + paintModel(session, { ...model, key: id, generation, busy: session.busy, result: session.result ?? model.result }); } -async function buildModel(session: DetailSession, id: string): Promise { - const projectRoot = session.deps.projectRoot(); +async function buildModel(session: DetailSession, id: string, request: number): Promise { + const projectRoot = session.projectRoot; + const generation = session.generation; const snapshot = await loadSnapshot(projectRoot === undefined ? {} : { projectRoot }); - const entry = snapshot.entries.find((candidate) => candidate.id === id); + const entry = snapshot.entries.find((candidate) => candidate.file === id || candidate.id === id); // The root the descriptor lives under, matched by path: two roots may share a // directory name, and the site link belongs to the one that holds the file. - const root = entry === undefined ? undefined : snapshot.roots.find((candidate) => entry.file.startsWith(candidate.path)); + const root = entry === undefined ? undefined : snapshot.roots.find((candidate) => { const relative = path.relative(candidate.path, entry.file); return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative); }); const ui = labels(); if (entry === undefined) { - return emptyModel(id, ui, t("This package is not in the local index any more.")); + const preview = session.preview; + const model = emptyModel(preview?.id ?? path.basename(id, ".lua"), ui, preview ? t("This is a search preview. Set an index path containing this package to read its complete details.") : t("This package is not in the local index any more.")); + model.preview = preview !== undefined; + model.previewVersion = preview?.version; + model.description = preview?.description; + model.registry = preview?.source ?? ""; + model.projectRoot = projectRoot; + model.blockedReason = preview ? t("Search previews cannot be added. Open a complete local descriptor first.") : t("The package descriptor is missing. Reread the local index."); + return model; } - const text = cachedDescriptorText(id) ?? (await readDescriptorText(entry.file)) ?? ""; + const text = cachedDescriptorText(entry.file) ?? (await readDescriptorText(entry.file)) ?? ""; const platform = platformKey(process.platform); // Keyed by the index revision as well as the path, so a refreshed index is not // answered from a parse of the previous one. - const cacheKey = `${snapshot.revision}\u0000${entry.file}`; + const cacheKey = `${snapshot.revision}\u0000${entry.file}\u0000${session.deps.isTrusted()}\u0000${session.deps.mcppExecutable(projectRoot)}\u0000${projectRoot ?? ""}`; let info = parsed.get(cacheKey); if (!parsed.has(cacheKey)) { - info = await readXpkg(session, entry); - parsed.set(cacheKey, info); + if (session.id === id && session.generation === generation && session.renderRequest === request && session.model?.loading) { + const lightweight: DetailModel = { + ...emptyModel(entry.id, ui, t("Reading package details…")), + key: id, generation, projectRoot, platform: platform ?? process.platform, + description: entry.description, registry: entry.registry, surface: entry.surface, surfaces: entry.surfaces, + licenses: entry.licenses, repo: entry.repo, badges: badgesOf(entry), + currentVersions: entry.versions[platform ?? ""] ?? [], latest: entry.version, + versions: versionGroups(entry.versions, platform).map((group) => ({ ...group, current: group.platform === platform })), + loading: true, blockedReason: t("Reading package details…"), dataSource: dataSource(entry, snapshot.roots.length), + }; + paintModel(session, lightweight); + } + info = await readXpkg(session, entry, projectRoot); + if (info !== undefined) { parsed.set(cacheKey, info); } + while (parsed.size > 100) { parsed.delete(parsed.keys().next().value!); } } const versions = info === undefined ? entry.versions : info.versions; @@ -308,44 +385,69 @@ async function buildModel(session: DetailSession, id: string): Promise ({ + file: file.path, startLine: 1, usageLine: 0, lines: file.text.split(/\r?\n/).slice(0, 80), + fileKey: session.exampleDocuments.capture(file), + })); + // Only verified lines from real example source are copyable. const realUsage = usageLinesFor(exampleFiles, entry.id); - const usage = realUsage.length > 0 ? realUsage : syntheticUsageLines(mergeSurfaces(info, text), entry.id); + const usage = realUsage; // The lock answers what a build actually resolved for each declared edge — // the same matching rule `installedFor` uses for this package itself. - const lock = lockPackageVersions(readInstalledLock(session)); + const lockText = projectRoot ? readTextTolerantly(path.join(projectRoot, "mcpp.lock")) : ""; + const manifestText = projectRoot ? readTextTolerantly(path.join(projectRoot, "mcpp.toml")) : ""; + if (projectRoot) { session.deps.onProjectFilesRead?.(projectRoot, manifestText, lockText); } + const lock = lockPackageVersions(lockText); + const declarations = readProjectDeclarations(manifestText); + const catalog = snapshot.entries.map((entry) => entry.id); + const dependencyGroups = { + normal: projectDependencyState(entry.id, declarations, lock, catalog, "normal"), + dev: projectDependencyState(entry.id, declarations, [], catalog, "dev"), + build: projectDependencyState(entry.id, declarations, [], catalog, "build"), + }; + const dependencyKind = dependencyGroups.normal.declared ? "normal" : dependencyGroups.dev.declared ? "dev" : dependencyGroups.build.declared ? "build" : "normal"; + const state = dependencyGroups[dependencyKind]; const dependencies = descriptorDependencies(text).map((dependency) => { - const short = dependency.id.slice(dependency.id.lastIndexOf(".") + 1); - const resolved = lock.find((entry) => entry.id === dependency.id || entry.id === short)?.version; - return resolved === undefined ? { ...dependency } : { ...dependency, resolved }; + const resolved = lock.find((entry) => entry.id === dependency.id)?.version; + const local = snapshot.entries.find((entry) => entry.id === dependency.id); + return { ...dependency, ...(resolved ? { resolved } : {}), ...(local ? { key: local.file } : {}) }; }); const model: DetailModel = { ui, + projectRoot, + manifestAvailable: !!projectRoot && existsSync(path.join(projectRoot, "mcpp.toml")), + platform: platform ?? process.platform, + projectState: state, + dependencyGroups, + dependencyKind, + blockedReason: !session.deps.isTrusted() ? t("Trust this workspace to run mcpp and add dependencies.") + : !projectRoot || !existsSync(path.join(projectRoot, "mcpp.toml")) ? t("Select an mcpp project before adding this library.") + : platform === undefined ? t("The current environment platform is not recognized by this index.") + : latest === undefined ? info === undefined ? t("Version availability could not be confirmed. Retry reading this descriptor.") : t("No version is published for the current environment platform.") + : undefined, + versionNotice: info === undefined ? t("Versions are from descriptor text; mcpp will confirm availability when adding.") : undefined, id: entry.id, name: entry.name, ...(entry.description === undefined ? {} : { description: entry.description }), licenses: entry.licenses, ...(entry.repo === undefined ? {} : { repo: entry.repo }), registry: entry.registry, - ...(entry.surface === undefined ? {} : { surface: entry.surface }), - surfaces: entry.surfaces, + ...(mergeSurfaces(info, text)[0] === undefined ? {} : { surface: mergeSurfaces(info, text)[0] }), + surfaces: mergeSurfaces(info, text), badges: badgesOf(entry), versions: groups, currentVersions: sortVersions(currentVersions), ...(latest === undefined ? {} : { latest }), - // §22: the page finally learns what the project already has. Without this - // the button said "Add" to a package mcpp.toml listed all along. - ...readInstalled(session, entry.id), ...(info?.standard === undefined ? {} : { standard: info.standard }), dependencies, includeDirs: info?.includeDirs ?? [], targets: (info?.targets ?? []).map((target) => target.name ?? "").filter((name) => name.length > 0), snippets, - ...(entry.example === undefined ? {} : { exampleProject: entry.example.project }), + ...(bundled ? { exampleProject: bundled.project, exampleProvenance: t("Bundled official index tests · commit {0}", bundled.commit.slice(0, 12)) } : entry.example ? { exampleProject: entry.example.project } : {}), usage, // One discreet link, and only when this root really is the index that // publishes those package pages (see `IndexRoot.site`). @@ -367,13 +469,13 @@ async function buildModel(session: DetailSession, id: string): Promise { +async function readXpkg(session: DetailSession, entry: LibraryEntry, projectRoot?: string): Promise { // `mcpp.path` is resource-scoped, so an untrusted workspace must not name // the program: the refusal degrades to the descriptor's own text. const result = await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, title: t("Reading {0}…", entry.id) }, () => - runMcpp(session.deps.isTrusted(), session.deps.mcppExecutable(), ["xpkg", "parse", entry.file, "--json"], session.deps.projectRoot(), { + runMcpp(session.deps.isTrusted(), session.deps.mcppExecutable(projectRoot), ["xpkg", "parse", entry.file, "--json"], projectRoot, { timeoutMs: PARSE_TIMEOUT_MS, maxBufferMiB: read("mcpp.runtime.maxOutputMiB"), }), @@ -424,42 +526,41 @@ function emptyModel(id: string, ui: Record, notice: string): Det }; } -/** - * The workspace's own answer about one package (§20.1, wired in §22): - * `mcpp.toml` first, `mcpp.lock` second, both read tolerantly — a missing or - * unreadable file is simply "not installed", never a broken page. The spread - * sets `installed` only when there is one. - */ -function readInstalled(session: DetailSession, id: string): { installed?: DetailInstalled } { - const root = session.deps.projectRoot(); - if (root === undefined) { - return {}; - } - const toml = readTextTolerantly(path.join(root, "mcpp.toml")); - const lock = readTextTolerantly(path.join(root, "mcpp.lock")); - const installed = installedFor(id, toml, lock); - return installed === undefined ? {} : { installed }; -} - -/** An unreadable or missing file reads as empty text; the view degrades, not breaks. */ function readTextTolerantly(file: string): string { - try { - return readFileSync(file, "utf8"); - } catch { - return ""; - } -} - -/** The workspace's `mcpp.lock` text, empty when there is none to read. */ -function readInstalledLock(session: DetailSession): string { - const root = session.deps.projectRoot(); - return root === undefined ? "" : readTextTolerantly(path.join(root, "mcpp.lock")); + try { return readFileSync(file, "utf8"); } catch { return ""; } } /** Every visible string, resolved once per model. */ function labels(): Record { return { [DETAIL_UI.htmlLang]: htmlLanguage(), + [DETAIL_UI.platforms]: t("Platforms and versions"), + [DETAIL_UI.kind]: t("Dependency type"), + [DETAIL_UI.kindNormal]: t("Project dependency"), + [DETAIL_UI.kindDev]: t("Development dependency"), + [DETAIL_UI.kindBuild]: t("Build dependency"), + [DETAIL_UI.addDevAction]: t("Add development dependency"), + [DETAIL_UI.buildHint]: t("Build dependencies are used by the build script. Copy this entry into mcpp.toml; mcpp add does not support this group yet."), + [DETAIL_UI.moveHint]: t("Changing this declaration requires a manifest edit. Copy the entry and update its dependency group in mcpp.toml."), + [DETAIL_UI.chooseProject]: t("Choose project"), + [DETAIL_UI.platform]: t("Environment platform"), + [DETAIL_UI.version]: t("Version"), + [DETAIL_UI.otherPlatforms]: t("Other platforms (read only)"), + [DETAIL_UI.declared]: t("Declared"), + [DETAIL_UI.locked]: t("Resolved in lockfile"), + [DETAIL_UI.sourceDependency]: t("Source dependency"), + [DETAIL_UI.unknownUsage]: t("No verified import or header path is available locally. See the repository or index site for usage."), + [DETAIL_UI.usageSource]: t("From the index's example source"), + [DETAIL_UI.source]: t("Source and diagnostics"), + [DETAIL_UI.loading]: t("Reading package details…"), + [DETAIL_UI.pending]: t("Adding dependency…"), + [DETAIL_UI.groupChange]: t("Changing dependency groups is not supported here. Edit mcpp.toml to move the declaration."), + [DETAIL_UI.output]: t("Show output"), + [DETAIL_UI.retry]: t("Retry reading details"), + [DETAIL_UI.openManifest]: t("Open mcpp.toml"), + [DETAIL_UI.openFile]: t("Source"), + [DETAIL_UI.readOnlySource]: t("Open read-only source"), + [DETAIL_UI.preview]: t("Search preview: only the fields returned by mcpp search are available."), [DETAIL_UI.title]: t("Library package {0}"), [DETAIL_UI.overview]: t("Build shape"), [DETAIL_UI.license]: t("License"), @@ -477,23 +578,23 @@ function labels(): Record { [DETAIL_UI.dependencies]: t("Dependencies"), [DETAIL_UI.dependenciesNone]: t("This descriptor declares no dependencies."), [DETAIL_UI.dependenciesHint]: t( - "What the descriptor declares. The resolved version lives in a project's mcpp.lock, not here.", + "Descriptor-level dependencies; the selected version may differ. Resolved values refer to this project's lockfile.", ), [DETAIL_UI.resolved]: t("resolved {0}"), [DETAIL_UI.code]: t("Example code"), [DETAIL_UI.codeNone]: t("This package has no test project in the index, so there is no example to show."), [DETAIL_UI.codeSource]: t("{0} · line {1}"), [DETAIL_UI.codeProject]: t("Example project: {0} — built and run by the index's CI, not written for this page."), - [DETAIL_UI.add]: t("Add to mcpp.toml"), + [DETAIL_UI.add]: t("Add dependency"), [DETAIL_UI.addDev]: t("dev dependency"), [DETAIL_UI.addLatest]: t("The version is required: mcpp accepts an exact version only."), [DETAIL_UI.switchTo]: t("Switch to {0}"), - [DETAIL_UI.alreadyAdded]: t("Already added"), + [DETAIL_UI.alreadyAdded]: t("Already declared"), [DETAIL_UI.installed]: t("added"), [DETAIL_UI.opening]: t("Opening {0}…"), [DETAIL_UI.addNoVersion]: t("This index publishes no version for this platform, so there is nothing to add."), [DETAIL_UI.command]: t("Command"), - [DETAIL_UI.usage]: t("Bring it into your code"), + [DETAIL_UI.usage]: t("Usage"), [DETAIL_UI.copy]: t("Copy"), [DETAIL_UI.copied]: t("Copied to the clipboard."), [DETAIL_UI.copying]: t("Copying…"), diff --git a/src/library/examples.ts b/src/library/examples.ts new file mode 100644 index 0000000..71a3c00 --- /dev/null +++ b/src/library/examples.ts @@ -0,0 +1,22 @@ +import snapshot from "../../data/library-examples.json"; +import { ReadOnlyDocuments } from "../webview/readOnlyDocuments"; +import { exampleCatalog, type CodeFile } from "./indexModel"; + +const catalog = exampleCatalog(snapshot.projects.map((project) => ({ + project: project.project, text: project.text, sources: project.files.map((file) => file.path), +}))); +const files = new Map(snapshot.projects.flatMap((project) => project.files.map((file) => [file.path, file.text] as const))); + +/** Official-index fallback only. Local checkouts use their own current tests. */ +export function bundledExample(id: string): { project: string; files: CodeFile[]; commit: string } | undefined { + const ref = catalog.get(id); + return ref ? { project: ref.project, commit: snapshot.commit, files: ref.paths.map((path) => ({ path, text: files.get(path)! })) } : undefined; +} + +export function bundledExampleText(path: string): string | undefined { return files.get(path.replace(/^\//, "")); } +export const EXAMPLE_SCHEME = "mcpp-example"; + +/** Immutable virtual documents keep local and bundled index sources equally read-only. */ +export class ExampleDocuments extends ReadOnlyDocuments { + constructor(captured?: (uri: string) => void) { super(EXAMPLE_SCHEME, captured); } +} diff --git a/src/library/indexLocator.ts b/src/library/indexLocator.ts index a29770a..afa6291 100644 --- a/src/library/indexLocator.ts +++ b/src/library/indexLocator.ts @@ -1,33 +1,7 @@ -/** - * Where the index lives, and how it is read **cheaply**. - * - * The catalog is a directory of Lua descriptors: - * `/.mcpp/registry/data//pkgs//.lua`. Three - * registries are installed on the reference machine (`mcpplibs` with 239 - * descriptors, `xim-pkgindex`, `xim-pkgindex-local`), and `mcpp index status` - * prints a table of them — which is deliberately **not** parsed: `--format json` - * is an unknown option there, and a second human-output parser is exactly what - * this project forbids. Globbing the data directory under `` gives the - * same answer without parsing anything. - * - * `` is `$MCPP_HOME`, then `~/.mcpp` — every one of them that holds a data - * directory — and when none does, whatever `mcpp self env --format json` - * reports, which is a machine shape and therefore allowed to be read. - * - * Two budgets shape this file: - * - * - **No process per package.** The list is read from the descriptor text - * (`src/library/indexModel.ts`); `mcpp xpkg parse --json` runs on demand, from - * the detail panel, never here. - * - **One read per index revision.** A snapshot is cached and invalidated by a - * cheap revision key (the `pkgs` directory's mtime, the `.mcpp-index-updated` - * marker and the descriptor count), so re-rendering the view after a filter - * change costs a few stats, not 239 file reads. - * - * A missing index is **not** an error: `locateIndexRoots()` returns `[]` and the - * caller renders the message this module hands it. +/** Local package discovery. Official indexes are separate from the xlings tool registry. + * Descriptor contents are cached independently from the project's dependency state. */ - +import { createHash } from "node:crypto"; import { promises as fs } from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; @@ -35,10 +9,10 @@ import * as vscode from "vscode"; import { runMcpp } from "../cli/process"; import { read } from "../config/access"; +import { projectDependencyState, readProjectDeclarations } from "./projectState"; import { descriptorEntry, descriptorId, - declaredDependencies, exampleCatalog, openkalFacetFor, parseDescriptorLua, @@ -68,6 +42,8 @@ export const INDEX_SITE = "https://mcpplibs.github.io/mcpp-index/packages"; export interface IndexRoot { /** The directory name, e.g. `mcpplibs`. */ registry: string; + kind: "official" | "custom"; + updatedAt?: number; /** The index root: it contains `pkgs`, and may contain `tests/` and `.xpkgindex/`. */ path: string; /** `/pkgs`. */ @@ -124,164 +100,83 @@ async function isFile(target: string): Promise { } /** One index root, or `undefined` when the directory is not one. */ -async function rootAt(directory: string): Promise { +async function rootAt(directory: string, official = false): Promise { const pkgs = path.join(directory, "pkgs"); if (!(await isDirectory(pkgs))) { return undefined; } + directory = await fs.realpath(directory); const registry = path.basename(directory); + const updated = await statSafe(path.join(directory, ".mcpp-index-updated")); const generator = await isFile(path.join(directory, ".xpkgindex", "plugins", "mcpp.py")); return { registry, + kind: official ? "official" : "custom", + ...(updated === undefined ? {} : { updatedAt: updated.mtimeMs }), path: directory, - pkgs, + pkgs: path.join(directory, "pkgs"), hasExamples: await isDirectory(path.join(directory, "tests", "examples")), hasOpenkal: await isFile(path.join(directory, ".xpkgindex", "openkal-compat.json")), - ...(registry === "mcpplibs" || generator ? { site: INDEX_SITE } : {}), + ...(official || generator ? { site: INDEX_SITE } : {}), }; } -/** - * The index roots to read. - * - * `mcpp.library.indexPath` wins when the user set it — it may name one index - * root (a directory with `pkgs/`), a `pkgs` directory itself, or a whole - * `/.mcpp/registry/data` directory of registries. With no setting, the - * default glob is the data directory under each known mcpp home. - * - * **Finding ``.** Two are tried, in order: `$MCPP_HOME` (the variable mcpp - * itself honours) and `~/.mcpp`. Both are listed when both hold something, and - * the view's footer names the registries it read, so a stale second home shows - * up as an extra source rather than as a wrong answer. When *neither* has a - * `registry/data` with anything in it, mcpp is asked directly — `mcpp self env - * --format json` reports `mcppHome`, which is the only authoritative answer for - * an install in a place nothing can guess. That probe costs one process **per - * session** and only runs when the cheap globs came up empty, so the ordinary - * machine never pays for it; it is also skipped in an untrusted workspace, - * because `mcpp.path` is a resource-scoped setting and a workspace must not - * choose a binary to run. - * - * `[]` means "no index found"; the caller turns that into the message rather - * than into an error, because a machine without the index is a normal machine. - * - * (`locateIndexRoots` is kept as an alias: the name reads better at the call - * site, the exported name is the one `extension.ts` uses.) - */ -export async function readIndexRoots(): Promise { +/** Explicit paths never silently fall back. Only the official library source is auto-discovered. */ +export async function readIndexRoots(projectRoot?: string): Promise { const configured = read("mcpp.library.indexPath"); - const candidates: string[] = []; - if (typeof configured === "string" && configured.trim().length > 0) { - const configured_path = path.resolve(expandHome(configured.trim())); - // A `pkgs` directory was named directly. - if (path.basename(configured_path) === "pkgs" && (await isDirectory(configured_path))) { - candidates.push(path.dirname(configured_path)); - } else { - const own = await rootAt(configured_path); - if (own !== undefined) { - candidates.push(own.path); - } else { - // A data directory: every registry inside it. - for (const entry of await readdirSafe(configured_path)) { - candidates.push(path.join(configured_path, entry)); - } - } - } - } else { - for (const data of await defaultDataDirectories()) { - for (const entry of await readdirSafe(data)) { - candidates.push(path.join(data, entry)); - } - } - } + return discoverIndexRoots(configured, configured.trim() ? path.join(os.homedir(), ".mcpp") : await activeHome(projectRoot)); +} - const roots: IndexRoot[] = []; - for (const candidate of candidates) { - const root = await rootAt(candidate); - if (root !== undefined && !roots.some((seen) => seen.path === root.path)) { - roots.push(root); +/** Filesystem seam used by the extension and fixture-based tests. */ +export async function discoverIndexRoots(configured: string, home: string): Promise { + if (configured.trim()) { + const target = path.resolve(expandHome(configured.trim())); + const own = await rootAt(path.basename(target) === "pkgs" ? path.dirname(target) : target); + const roots = own === undefined + ? (await Promise.all((await readdirSafe(target)).sort().map((name) => rootAt(path.join(target, name))))) + .filter((root): root is IndexRoot => root !== undefined) + : [own]; + if (roots.length === 0) { + throw new Error(`No package index (pkgs/) at ${target}`); } + return roots.filter((root, index) => roots.findIndex((other) => other.path === root.path) === index); + } + for (const directory of [path.join(home, "indices", "mcpplibs"), path.join(home, "registry", "data", "mcpplibs")]) { + const root = await rootAt(directory, true); + if (root !== undefined) { return [root]; } } - return roots.sort((a, b) => (a.registry < b.registry ? -1 : a.registry > b.registry ? 1 : 0)); + return []; } -/** Set once per session, so `mcpp self env` runs at most once. */ -let probedHome: string | undefined; -let probeAttempted = false; +let homeProbe: { key: string; promise: Promise } | undefined; -/** The homes mcpp may have used, most specific first, without duplicates. */ -function candidateHomes(): string[] { - const homes: string[] = []; - const fromEnvironment = process.env.MCPP_HOME?.trim(); - if (fromEnvironment !== undefined && fromEnvironment.length > 0) { - homes.push(fromEnvironment); - } - homes.push(path.join(os.homedir(), ".mcpp")); - if (probedHome !== undefined) { - homes.push(probedHome); - } - return [...new Set(homes)]; +/** CLI also supports self-contained installs: ask it once rather than merge unrelated homes. */ +async function activeHome(projectRoot?: string): Promise { + const explicit = process.env.MCPP_HOME?.trim(); + if (explicit) { return path.resolve(expandHome(explicit)); } + return (await mcppHomeFromCli(projectRoot)) ?? path.join(os.homedir(), ".mcpp"); } -/** - * The `/registry/data` directories worth listing. - * - * The cheap answer first: a data directory that already holds something is the - * answer, and no process is started. Only when all of them are missing or empty - * is `mcpp self env` asked where its home is — and its answer is picked up by - * re-reading the candidate list, because the probe is what adds the home to it. - */ -async function defaultDataDirectories(): Promise { - const candidates = (): string[] => candidateHomes().map((home) => path.join(home, "registry", "data")); - const present: string[] = []; - for (const data of candidates()) { - if ((await readdirSafe(data)).length > 0) { - present.push(data); - } - } - if (present.length > 0) { - return present; - } - await mcppHomeFromCli(); - return [...new Set(candidates())]; +export function invalidateIndexCache(): void { + cache = undefined; + homeProbe = undefined; } -/** - * `mcpp self env --format json`, for the one field that says where the index is. - * - * It runs at most once per session and never in an untrusted workspace. A - * missing or failing mcpp is not an error here: the caller falls back to the - * directories it already has, and the view says "no index" the way it does when - * there is none. - */ -async function mcppHomeFromCli(): Promise { - if (probeAttempted) { - return probedHome; - } - probeAttempted = true; - const executable = read("mcpp.path").trim(); - // `mcpp.path` is resource-scoped, so the probe goes through the trust-gated - // seam; a refusal is the same "no answer" as a failing mcpp. - const result = await runMcpp( - vscode.workspace.isTrusted, - executable.length === 0 ? "mcpp" : executable, - ["self", "env", "--format", "json"], - undefined, - { timeoutMs: SELF_ENV_TIMEOUT_MS, maxBufferMiB: 1 }, - ); - if (result === undefined || result.exitCode !== 0) { - return undefined; - } - const env = parseSelfEnv(result.stdout); - if (env === undefined) { - return undefined; - } - if (env.mcppHome !== undefined) { - probedHome = env.mcppHome; - } else if (env.registry !== undefined) { - // `registry` is `/registry`; the home is what the other paths hang off. - probedHome = path.dirname(env.registry); - } - return probedHome; +/** Share concurrent probes, with separate answers for different configured executables/projects. */ +async function mcppHomeFromCli(projectRoot?: string): Promise { + const resource = projectRoot === undefined ? undefined : vscode.Uri.file(projectRoot); + const executable = read("mcpp.path", resource).trim() || "mcpp"; + const key = `${executable}\u0000${projectRoot ?? ""}\u0000${vscode.workspace.isTrusted}`; + if (homeProbe?.key === key) { return homeProbe.promise; } + const promise = (async () => { + const result = await runMcpp(vscode.workspace.isTrusted, executable, ["self", "env", "--format", "json"], projectRoot, + { timeoutMs: SELF_ENV_TIMEOUT_MS, maxBufferMiB: 1 }); + if (result === undefined || result.exitCode !== 0) { return undefined; } + const env = parseSelfEnv(result.stdout); + return env?.mcppHome ?? (env?.registry === undefined ? undefined : path.dirname(env.registry)); + })(); + homeProbe = { key, promise }; + return promise; } export { readIndexRoots as locateIndexRoots }; @@ -324,23 +219,15 @@ async function descriptorFiles(pkgs: string): Promise { return out.sort(); } -/** - * A cheap key that changes when the index does. - * - * The `pkgs` directory's own mtime catches an added or removed letter - * directory; `.mcpp-index-updated` is written whenever mcpp refreshes the index, - * which catches a changed descriptor body. The descriptor count is included so a - * tree that stops being updated still costs one full read when its size changes. - */ +/** Include source identity and every descriptor stat, including checkout edits without a marker. */ async function revisionOf(roots: readonly IndexRoot[]): Promise { const parts: string[] = []; for (const root of roots) { - const pkgs = await statSafe(root.pkgs); - const marker = await statSafe(path.join(root.path, ".mcpp-index-updated")); - const letters = await readdirSafe(root.pkgs); - parts.push(`${root.registry}:${pkgs?.mtimeMs ?? 0}:${marker?.mtimeMs ?? 0}:${letters.length}`); + const files = await descriptorFiles(root.pkgs); + const stats = await Promise.all(files.map(async (file) => `${file}:${JSON.stringify(await statSafe(file))}`)); + parts.push(`${root.path}:${root.kind}:${root.updatedAt ?? ""}:${stats.join(";")}`); } - return parts.join("|"); + return createHash("sha256").update(parts.join("|")).digest("hex"); } async function statSafe(target: string): Promise<{ mtimeMs: number; size: number } | undefined> { @@ -416,17 +303,14 @@ async function readOpenkal(roots: readonly IndexRoot[]): Promise> { - if (projectRoot === undefined) { - return new Set(); - } - try { - const text = await fs.readFile(path.join(projectRoot, "mcpp.toml"), "utf8"); - return new Set(declaredDependencies(text)); - } catch { - return new Set(); - } +/** Project overlay is always fresh and never changes the descriptor cache. */ +async function withProject(snapshot: LibrarySnapshot, projectRoot?: string): Promise { + const text = projectRoot === undefined ? "" : await fs.readFile(path.join(projectRoot, "mcpp.toml"), "utf8").catch(() => ""); + const declarations = readProjectDeclarations(text); + const ids = snapshot.entries.map((entry) => entry.id); + return { ...snapshot, entries: snapshot.entries.map((entry) => ({ + ...entry, added: (["normal", "dev", "build"] as const).some((kind) => projectDependencyState(entry.id, declarations, [], ids, kind).declared), + })) }; } interface Cache { @@ -441,15 +325,14 @@ let cache: Cache | undefined; /** * The list, read from disk only when the revision changed. * - * `projectRoot` is the workspace folder whose `mcpp.toml` decides the "already - * declared" state; it is part of the cache key, because switching workspaces - * changes the answer without changing the index. + * Only descriptor contents are cached. Project declarations are reapplied on + * every read, so disk writes and project switches cannot reuse old badges. */ export async function loadSnapshot(options: { projectRoot?: string; platform?: string } = {}): Promise { - const roots = await readIndexRoots(); - const revision = `${await revisionOf(roots)}|${options.projectRoot ?? ""}`; + const roots = await readIndexRoots(options.projectRoot); + const revision = `${await revisionOf(roots)}|${options.platform ?? platformKey(process.platform)}`; if (cache !== undefined && cache.revision === revision) { - return cache.snapshot; + return withProject(cache.snapshot, options.projectRoot); } if (roots.length === 0) { const empty: LibrarySnapshot = { entries: [], roots, revision }; @@ -458,21 +341,18 @@ export async function loadSnapshot(options: { projectRoot?: string; platform?: s } const platform = options.platform ?? platformKey(process.platform); - const added = await readAdded(options.projectRoot); const entries: LibraryEntry[] = []; const texts = new Map(); /** Example refs, keyed by package id, from every root that has examples. */ const exampleRefs = new Map(); for (const root of roots) { for (const [id, ref] of exampleCatalog(await readExamples(root))) { - if (!exampleRefs.has(id)) { - exampleRefs.set(id, ref); - } + exampleRefs.set(`${root.path}\u0000${id}`, ref); } } - const openkal = await readOpenkal(roots); for (const root of roots) { + const openkal = await readOpenkal([root]); for (const file of await descriptorFiles(root.pkgs)) { let text: string; try { @@ -485,7 +365,7 @@ export async function loadSnapshot(options: { projectRoot?: string; platform?: s // `descriptorEntry` decides the identity; the example and openkal lookups // below must use that same id, so it is computed the same way here. const id = descriptorId(fields, fileName).id; - const example = exampleRefs.get(id); + const example = exampleRefs.get(`${root.path}\u0000${id}`); const facet = openkalFacetFor(openkal, id); const entry = descriptorEntry({ fileName, @@ -493,19 +373,19 @@ export async function loadSnapshot(options: { projectRoot?: string; platform?: s file, text, platform, - added: added.has(id), + added: false, ...(example === undefined ? {} : { example }), ...(facet === undefined ? {} : { openkal: facet }), }); entries.push(entry); - texts.set(entry.id, text); + texts.set(entry.file, text); } } entries.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); const snapshot: LibrarySnapshot = { entries, roots, revision }; cache = { revision, snapshot, texts }; - return snapshot; + return withProject(snapshot, options.projectRoot); } /** The descriptor text of one package, from the cache when possible. */ @@ -518,8 +398,8 @@ export async function readDescriptorText(file: string): Promise { const catalog = new Map(); + const priorities = new Map(); const sorted = [...inputs].sort((a, b) => (a.project < b.project ? -1 : a.project > b.project ? 1 : 0)); for (const input of sorted) { if (input.sources.length === 0) { @@ -949,8 +950,12 @@ export function exampleCatalog(inputs: readonly ExampleInput[]): Map; rows: LibraryRow[]; + selectedKey?: string; query: string; networkSearch: boolean; /** @@ -71,6 +74,9 @@ export interface LibraryModel { networkSearchSetting: string; /** The data source, in plain words, for the footer. */ dataSource: string; + sourceDetails?: string; + status?: string; + projectNotice?: string; /** `{0}` visible of `{1}` known, already localized; the header count uses it. */ countTemplate: string; /** Rows before filtering; the count line shows it against what is visible. */ @@ -112,6 +118,12 @@ export const LIBRARY_UI = { count: "library.count", open: "library.open", refresh: "library.refresh", + sources: "library.sources", + other: "library.other", + settings: "library.settings", + update: "library.update", + output: "library.output", + clear: "library.clear", } as const; /** One badge key's `ui` key, so the renderer has a single lookup table. */ @@ -124,23 +136,13 @@ const BADGE_KEY_UI: Readonly> = { "openkal-platform": LIBRARY_UI.badgeOpenkalPlatform, }; -/** - * What the document says to the host. - * - * There deliberately **no `ready` message**. The document is the data — every - * row, badge and count is already in it — so a "I have loaded" announcement can - * only invite the host to render the same thing again. It did exactly that, and - * because assigning `webview.html` reloads the document while every render mints - * a fresh CSP nonce, the page never matched the previous one: the view reloaded - * itself forever (flicker, unclickable rows, a pegged CPU). The host now also - * refuses to re-assign an identical document — both rules live in - * `src/webview/document.ts` (`WebviewDocument`) and `src/webview/render.ts` - * (`documentNeedsRender`) — so this cannot come back by accident. +/** A mounted handshake replays data in place after the initial document loads. + * The host never assigns webview.html in response, so it cannot create a reload loop. */ export type LibraryMessage = - | { type: "refresh" } + | { type: "refresh" | "settings" | "update" | "output" | "mounted" } | { type: "search"; query: string } - | { type: "networkSearch"; enabled: boolean } + | { type: "networkSearch"; enabled: boolean; token?: string } | { type: "open"; id: string }; export type UiLabel = (key: string) => string; @@ -187,7 +189,7 @@ export function badgeLabels(badges: readonly BadgeKey[], label: UiLabel): string } function renderRow(row: LibraryRow, label: UiLabel): string { - const badges = badgeLabels(row.badges, label); + const badges: string[] = []; const openTitle = fill(label, LIBRARY_UI.open, [row.id]); const surface = row.surface === undefined ? undefined : surfaceLabel(row.surface, label); const version = @@ -197,14 +199,14 @@ function renderRow(row: LibraryRow, label: UiLabel): string { ...badges.map((badge) => `${escapeHtml(badge)}`), row.added ? `${escapeHtml(label(LIBRARY_UI.added))}` : "", row.unreadable ? `${escapeHtml(label(LIBRARY_UI.unreadable))}` : "", - row.crossRegistry === true ? `${escapeHtml(label(LIBRARY_UI.crossRegistry))}` : "", + row.crossRegistry === true || row.source ? `${escapeHtml(row.source ?? label(LIBRARY_UI.crossRegistry))}` : "", ] .filter((part) => part.length > 0) .join(""); const description = row.description === undefined ? "" : row.description; return [ `
  • `, - ` `, ` `, + ` ${escapeHtml(fill(label, LIBRARY_UI.count, [model.rows.filter((row) => !row.crossRegistry && rowMatchesClient(model, row)).length, model.rows.filter((row) => !row.crossRegistry).length]))}`, ``, ].join("\n"); } function renderList(model: LibraryModel, label: UiLabel): string { - if (model.rows.length === 0) { - return `

    ${escapeHtml(model.notice ?? label(LIBRARY_UI.noResults))}

    `; - } - return [ - `
      `, - model.rows.map((row) => renderRow(row, label)).join("\n"), - `
    `, - ``, - ].join("\n"); + const local = model.rows.filter((row) => !row.crossRegistry); + const extra = model.rows.filter((row) => row.crossRegistry); + return `
    + ${model.projectNotice ? `

    ${escapeHtml(model.projectNotice)}

    ` : ""} +
      ${local.map((row) => renderRow(row, label)).join("\n")}
    +
    +

    ${escapeHtml(label(LIBRARY_UI.other))} (${extra.length})

    +
      ${extra.map((row) => renderRow(row, label)).join("\n")}
    +
    +

    ${escapeHtml(model.notice ?? label(LIBRARY_UI.noResults))}

    +

    ${escapeHtml(model.status ?? "")}

    +
    `; } /** @@ -272,72 +279,98 @@ function clientScript(initialModel: string): string { "use strict"; var api = typeof acquireVsCodeApi === "function" ? acquireVsCodeApi() : undefined; var state = ${initialModel}; + var saved = api && api.getState ? api.getState() || {} : {}; var searchInput = document.getElementById("library-search"); var networkInput = document.getElementById("library-network"); - var list = document.getElementById("library-list"); - var empty = document.getElementById("library-empty"); - var countElement = document.getElementById("library-count"); - - function post(message) { - if (api) { api.postMessage(message); } + var networkToken; + var networkSequence = 0; + var selected = state.selectedKey || saved.selected || ""; + if (searchInput && typeof saved.query === "string") { searchInput.value = saved.query; } + function post(message) { if (api) { api.postMessage(message); } } + function persist() { + if (api && api.setState) { api.setState({ query: searchInput.value, selected: selected, scroll: window.scrollY }); } } - - // Every whitespace-separated word has to appear in the row's haystack, which - // the host builds from the id, the name, the description and the usage labels. - function rowMatches(row, query) { - if (query.length === 0) { return true; } - var haystack = row.getAttribute("data-haystack") || ""; - var parts = query.split(/\\s+/); - for (var index = 0; index < parts.length; index += 1) { - if (parts[index].length > 0 && haystack.indexOf(parts[index]) < 0) { return false; } - } - return true; - } - function apply() { - var query = searchInput ? searchInput.value.trim().toLowerCase() : ""; - var any = false; - var visible = 0; + document.querySelector('[data-action="clear"]').hidden = searchInput.value.length === 0; + var query = searchInput.value.trim().toLowerCase(); + var parts = query.split(/\\s+/); + var rows = Array.from(document.querySelectorAll("[data-row]")); + var local = 0; + var extra = 0; + rows.forEach(function (row) { + var haystack = row.getAttribute("data-haystack") || ""; + var shown = parts.every(function (part) { return !part || haystack.indexOf(part) >= 0; }); + row.parentNode.hidden = !shown; + if (shown) { if (row.getAttribute("data-cross") === "true") { extra++; } else { local++; } } + if (row.getAttribute("data-row") === selected) { row.setAttribute("aria-current", "true"); } + else { row.removeAttribute("aria-current"); } + }); + var list = document.getElementById("library-list"); if (list) { - var rows = list.querySelectorAll("[data-row]"); - for (var index = 0; index < rows.length; index += 1) { - var shown = rowMatches(rows[index], query); - rows[index].parentNode.hidden = !shown; - if (shown) { any = true; visible += 1; } - } - list.hidden = !any; - } - if (empty) { empty.hidden = any || !list; } - if (countElement) { - var template = state && state.countTemplate ? state.countTemplate : ""; - var total = state && state.total ? state.total : 0; - countElement.textContent = template.split("{0}").join(String(visible)).split("{1}").join(String(total)); + Array.from(list.children).sort(function (a, b) { + function rank(item) { + var id = item.firstElementChild.getAttribute("data-id").toLowerCase(); + var name = id.slice(id.lastIndexOf(".") + 1); + return !query ? 2 : id === query || name === query ? 0 : id.indexOf(query) === 0 || name.indexOf(query) === 0 ? 1 : 2; + } + return rank(a) - rank(b) || a.firstElementChild.getAttribute("data-id").localeCompare(b.firstElementChild.getAttribute("data-id")); + }).forEach(function (item) { list.appendChild(item); }); } + var empty = document.getElementById("library-empty"); + if (empty) { empty.hidden = local + extra > 0; } + var group = document.getElementById("library-extra"); + if (group) { group.hidden = extra === 0; } + document.getElementById("library-count").textContent = state.countTemplate.split("{0}").join(String(local)).split("{1}").join(String(state.localTotal)); + persist(); } - - if (searchInput) { - searchInput.addEventListener("input", function () { - apply(); - post({ type: "search", query: searchInput.value }); - }); - } - if (networkInput) { - networkInput.addEventListener("change", function () { - post({ type: "networkSearch", enabled: networkInput.checked === true }); - }); - } + searchInput.addEventListener("input", function () { apply(); post({ type: "search", query: searchInput.value }); }); + networkInput.addEventListener("change", function () { networkToken = Date.now() + "-" + (++networkSequence); post({ type: "networkSearch", enabled: networkInput.checked, token: networkToken }); }); document.addEventListener("click", function (event) { - var target = event.target; - if (!target || typeof target.closest !== "function") { return; } - var refresh = target.closest("[data-action]"); - if (refresh && refresh.getAttribute("data-action") === "refresh") { - post({ type: "refresh" }); - return; + var target = event.target.closest && event.target.closest("[data-action], [data-row]"); + if (!target) { return; } + var action = target.getAttribute("data-action"); + if (action === "clear") { searchInput.value = ""; searchInput.focus(); apply(); post({ type: "search", query: "" }); return; } + if (action) { post({ type: action }); return; } + selected = target.getAttribute("data-row"); apply(); post({ type: "open", id: selected }); + }); + document.addEventListener("keydown", function (event) { + if (event.key === "Escape" && event.target === searchInput) { + searchInput.value = ""; apply(); post({ type: "search", query: "" }); } - var row = target.closest("[data-row]"); - if (row) { post({ type: "open", id: row.getAttribute("data-row") }); } + if (event.key !== "ArrowDown" && event.key !== "ArrowUp") { return; } + var rows = Array.from(document.querySelectorAll("[data-row]")).filter(function (row) { return !row.parentNode.hidden; }); + if (!rows.length || (event.target !== searchInput && !event.target.hasAttribute("data-row"))) { return; } + event.preventDefault(); + var index = rows.indexOf(document.activeElement); + var next = event.key === "ArrowDown" ? Math.min(rows.length - 1, index + 1) : Math.max(0, index - 1); + rows[next].focus(); + }); + window.addEventListener("scroll", persist); + window.addEventListener("message", function (event) { + var payload = event.data; + if (payload && payload.type === "networkSearchSaved") { if (payload.token === networkToken) { networkToken = undefined; networkInput.checked = payload.enabled; } return; } + if (!payload || payload.type !== "libraryUpdate") { return; } + var scroll = window.scrollY; + var focused = document.activeElement && document.activeElement.getAttribute("data-row"); + var details = document.querySelector("footer details"); + var expanded = details && details.open; + var doc = new DOMParser().parseFromString(payload.html, "text/html"); + ["library-results", "library-footer"].forEach(function (id) { + var old = document.getElementById(id); var next = doc.getElementById(id); + if (old && next) { old.replaceWith(next); } + }); + state = payload.state; + if (state.selectedKey) { selected = state.selectedKey; } + if (!networkToken) { networkInput.checked = payload.networkSearch; } + apply(); + var nextDetails = document.querySelector("footer details"); if (nextDetails) { nextDetails.open = expanded; } + if (focused) { Array.from(document.querySelectorAll("[data-row]")).some(function (row) { if (row.getAttribute("data-row") === focused) { row.focus(); return true; } return false; }); } + window.scrollTo(0, scroll); }); apply(); + window.scrollTo(0, saved.scroll || 0); + post({ type: "mounted" }); + post({ type: "search", query: searchInput.value }); })();`; } @@ -348,10 +381,12 @@ function clientScript(initialModel: string): string { * embedding the model again would double the page for nothing. `<` is escaped in * `embedJson`, so even this cannot close the script element. */ -function clientState(model: LibraryModel): Record { +export function libraryClientState(model: LibraryModel): Record { return { countTemplate: model.countTemplate, + selectedKey: model.selectedKey, total: model.total, + localTotal: model.rows.filter((row) => !row.crossRegistry).length, query: model.query, }; } @@ -380,20 +415,24 @@ export function renderLibraryHtml(model: LibraryModel, assets: LibraryAssets): s ${escapeHtml(label(LIBRARY_UI.title))} -
    +

    ${escapeHtml(label(LIBRARY_UI.title))}

    -

    ${escapeHtml(count)}

    ${renderToolbar(model, label)}
    ${renderList(model, label)}
    -