[
  {
    "path": ".JuliaFormatter.toml",
    "content": ""
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/DocPreviewCleanup.yml",
    "content": "name: Doc Preview Cleanup\n\non:\n  pull_request:\n    types: [closed]\n\njobs:\n  doc-preview-cleanup:\n    runs-on: ubuntu-slim\n    permissions:\n      contents: write\n    steps:\n      - name: Checkout gh-pages branch\n        uses: actions/checkout@v6\n        with:\n          ref: gh-pages\n      - name: Delete preview and history + push changes\n        run: |\n            if [ -d \"previews/PR$PRNUM\" ]; then\n              git config user.name \"Documenter.jl\"\n              git config user.email \"documenter@juliadocs.github.io\"\n              git rm -rf \"previews/PR$PRNUM\"\n              git commit -m \"delete preview\"\n              git branch gh-pages-new $(echo \"delete history\" | git commit-tree HEAD^{tree})\n              git push --force origin gh-pages-new:gh-pages\n            fi\n        env:\n            PRNUM: ${{ github.event.number }}\n\n# copied from here:\n# https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#gh-pages-Branch\n"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "content": "name: TagBot\n\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\n    inputs:\n      lookback:\n        default: 3\n\njobs:\n  TagBot:\n    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: JuliaRegistries/TagBot@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          ssh: ${{ secrets.SSH_KEY }}\n          changelog: |\n            {% if custom %}\n            {{ custom }}\n            {% endif %}\n\n            The changes are documented in the [`CHANGELOG.md`](https://github.com/JuliaTesting/Aqua.jl/blob/{{ version }}/CHANGELOG.md) file.\n\n            {% if previous_release %}\n            [Diff since {{ previous_release }}]({{ compare_url }})\n            {% endif %}\n"
  },
  {
    "path": ".github/workflows/code-style.yml",
    "content": "name: Code style\n\non:\n  pull_request:\n\njobs:\n  code-style:\n    runs-on: ubuntu-slim\n    steps:\n      - uses: tkf/julia-code-style-suggesters@v1\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Documentation\n\non:\n  push:\n    branches:\n      - master\n      - 'release-*'\n    tags: '*'\n  pull_request:\n  workflow_dispatch:\n\n# needed to allow julia-actions/cache to delete old caches that it has created\npermissions:\n  actions: write\n  contents: read\n\nconcurrency:\n  # group by workflow and ref; the last slightly strange component ensures that for pull\n  # requests, we limit to 1 concurrent job, but for the master branch we don't\n  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }}\n  # Cancel intermediate builds, but only if it is a pull request build.\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\njobs:\n  Documenter:\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v6\n      - uses: julia-actions/setup-julia@v2\n        with:\n          version: 1\n      - uses: julia-actions/cache@v3\n      - uses: julia-actions/julia-docdeploy@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          DOCUMENTER_KEY: ${{ secrets.SSH_KEY }}\n"
  },
  {
    "path": ".github/workflows/enforce-labels.yml",
    "content": "name: Enforce PR labels\n\npermissions:\n  contents: read\non:\n  pull_request:\n    types: [labeled, unlabeled, opened, reopened, edited, synchronize]\njobs:\n  enforce-labels:\n    name: Check for blocking labels\n    runs-on: ubuntu-latest\n    timeout-minutes: 2\n    steps:\n    - uses: yogevbd/enforce-label-action@2.2.2\n      with:\n        REQUIRED_LABELS_ANY: \"changelog: added,changelog: not needed,release\"\n        REQUIRED_LABELS_ANY_DESCRIPTION: \"Select at least one label ['changelog: added','changelog: not needed','release']\"\n        BANNED_LABELS: \"changelog: missing,DO NOT MERGE\"\n        BANNED_LABELS_DESCRIPTION: \"A PR should not be merged with `DO NOT MERGE` or `changelog: missing` labels\"\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Run tests\n\non:\n  push:\n    branches:\n      - master\n      - 'release-*'\n    tags: '*'\n  pull_request:\n  workflow_dispatch:\n\n# needed to allow julia-actions/cache to delete old caches that it has created\npermissions:\n  actions: write\n  contents: read\n\nconcurrency:\n  # group by workflow and ref; the last slightly strange component ensures that for pull\n  # requests, we limit to 1 concurrent job, but for the master branch we don't\n  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }}\n  # Cancel intermediate builds, but only if it is a pull request build.\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\njobs:\n  test:\n    name: Test Julia ${{ matrix.julia-version }} ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 15\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [\"ubuntu-latest\"]\n        julia-version:\n          - '1.12-nightly'\n          - '1.11'\n          - '1.10'\n          - '1.9'\n          - '1.8'\n          - '1.7'\n          - '1.6'\n          - 'nightly'\n        include:\n          - os: windows-latest\n            julia-version: '1'\n          - os: windows-latest\n            julia-version: '1.6'\n          - os: windows-latest\n            julia-version: '1.12-nightly'\n          - os: windows-latest\n            julia-version: 'nightly'\n          - os: macOS-latest\n            julia-version: '1'\n          - os: macOS-latest\n            julia-version: '1.12-nightly'\n          - os: macOS-latest\n            julia-version: 'nightly'\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          # For Codecov, we must also fetch the parent of the HEAD commit to\n          # be able to properly deal with PRs / merges\n          fetch-depth: 2\n      - name: Setup julia\n        uses: julia-actions/setup-julia@v2\n        with:\n          version: ${{ matrix.julia-version }}\n      - uses: julia-actions/cache@v3\n      - uses: julia-actions/julia-runtest@v1\n        with:\n          depwarn: error\n      - uses: julia-actions/julia-processcoverage@v1\n      - uses: codecov/codecov-action@v6\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.jl.*.cov\n*.jl.cov\n*.jl.mem\n.DS_Store\n/docs/build/\n/docs/site/\n/docs/src/release-notes.md\nManifest.toml\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Release notes\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## Version [v1.0.0] - unreleased\n\n### Changed\n\n- Fix some world age issues in `test_ambiguities`. ([#366])\n- The minimum supported julia version is increased to 1.6. ([#328])\n\n## Version [v0.8.14] - 2025-08-04\n\n### Changed\n\n- Adapt to internal method table changes in Julia 1.12 and later. ([#344])\n\n## Version [v0.8.13] - 2025-05-28\n\n### Changed\n\n- Adapt to internal method table changes in Julia 1.12 and later. ([#334])\n\n## Version [v0.8.12] - 2025-05-05\n\n### Changed\n\n- Add `test_undocumented_names` to verify that all public symbols have docstrings (not including the module itself). This test is not enabled by default in `test_all`. ([#313])\n\n## Version [v0.8.11] - 2025-02-06\n\n### Changed\n\n- Avoid deprecation warnings concerning `Base.isbindingresolved` with julia nightly. ([#322])\n\n## Version [v0.8.10] - 2025-01-26\n\n### Changed\n\n- No longer call `@testset` for testsets that are skipped. ([#319])\n\n\n## Version [v0.8.9] - 2024-10-15\n\n### Changed\n\n- Change `test_ambiguities` to only return ambiguities that happen in the target package. ([#309])\n\n\n## Version [v0.8.8] - 2024-10-10\n\n### Changed\n\n- Improved the documentation of `test_persisten_tasks`. ([#297])\n\n\n## Version [v0.8.7] - 2024-04-09\n\n- Reverted [#285], which was originally released in [v0.8.6], but caused a regression. ([#287], [#288])\n\n\n## Version [v0.8.6] - 2024-04-09\n\n### Changed\n\n- The output of `test_ambiguities` now gets printed to stderr instead of stdout. ([#281])\n\n\n## Version [v0.8.5] - 2024-04-03\n\n### Changed\n\n- When supplying `broken = true` to `test_ambiguities`, `test_undefined_exports`, `test_piracies`, or `test_unbound_args`, the output is shortened. In particular, the list of offending instances is no longer printed. To get the full output, set `broken = false`. ([#272])\n- Use [Changelog.jl](https://github.com/JuliaDocs/Changelog.jl) to generate the changelog, and add it to the documentation. ([#277], [#279])\n- `test_project_extras` prints failures the same on all julia versions. In particular, 1.11 and nightly are no outliers. ([#275])\n\n\n## Version [v0.8.4] - 2023-12-01\n\n### Added\n\n- `test_persistent_tasks` now accepts an optional `expr` to run in the precompile package. ([#255])\n  + The `expr` option lets you test whether your precompile script leaves any dangling Tasks\n  or Timers, which would make it unsafe to use as a dependency for downstream packages.\n\n\n## Version [v0.8.3] - 2023-11-29\n\n### Changed\n\n- `test_persistent_tasks` is now less noisy. ([#256])\n- Completely overhauled the documentation. Every test now has its dedicated page. ([#250])\n\n\n## Version [v0.8.2] - 2023-11-16\n\n### Changed\n\n- `test_persistent_tasks` no longer clears the environment of the subtask. Instead, it modifies `LOAD_PATH` directly to make stdlibs work. ([#241])\n\n\n## Version [v0.8.1] - 2023-11-16\n\n### Changed\n\n- `test_persistent_tasks` now redirects stdout and stderr of the created subtask. Furthermore, the environment of the subtask gets cleared to allow default values for `JULIA_LOAD_PATH` to work. ([#240])\n\n\n## Version [v0.8.0] - 2023-11-15\n\n### Added\n\n- Two additions check whether packages might block precompilation on Julia 1.10 or higher: ([#174])\n  + `test_persistent_tasks` tests whether \"your\" package can safely be used as a dependency for downstream packages. This test is enabled for the default testsuite `test_all`, but you can opt-out by supplying `persistent_tasks=false` to `test_all`. [BREAKING]\n  + `find_persistent_tasks_deps` is useful if \"your\" package hangs upon precompilation: it runs `test_persistent_tasks` on all the things you depend on, and may help isolate the culprit(s).\n\n### Changed\n\n- In `test_deps_compat`, the two subtests `check_extras` and `check_weakdeps` are now run by default. ([#202]) [BREAKING]\n- `test_deps_compat` now requires compat entries for all dependencies. Stdlibs no longer get ignored. This change is motivated by similar changes in the General registry. ([#215]) [BREAKING]\n- `test_ambiguities` now excludes the keyword sorter of all `exclude`d functions with keyword arguments as well. ([#203])\n- `test_piracy` is renamed to `test_piracies`. ([#230]) [BREAKING]\n- `test_ambiguities` and `test_piracies` now return issues in a defined order. This order may change in a patch release of Aqua.jl. ([#233])\n- Improved the message for `test_project_extras` failures. ([#234])\n- `test_deps_compat` now requires a compat entry for `julia` This can be disabling by setting `compat_julia = false`. ([#236]) [BREAKING]\n\n### Removed\n\n- `test_project_toml_formatting` has been removed. Thus, the kwarg `project_toml_formatting` to `test_all` no longer exists. ([#209]) [BREAKING]\n\n\n## Version [v0.7.4] - 2023-10-24\n\n### Added\n\n- `test_deps_compat` has two new kwargs `check_extras` and `check_weakdeps` to extend the test to these dependency categories. They are not run by default. ([#200])\n\n### Changed\n\n- The docstring for `test_stale_deps` explains the situation with package extensions. ([#203])\n- The logo of Aqua.jl has been updated. ([#128])\n\n\n## Version [v0.7.3] - 2023-09-25\n\n### Added\n\n- `test_deps_compat` has a new kwarg `broken` to mark the test as broken using `Test.@test_broken`. ([#193])\n\n### Fixed\n\n- `test_piracy` no longer prints warnings for methods where the third argument is a `TypeVar`. ([#188])\n\n\n## Version [v0.7.2] - 2023-09-19\n\n### Changed\n\n- `test_undefined_exports` additionally prints the modules of the undefined exports in the failure report. ([#177])\n\n\n## Version [v0.7.1] - 2023-09-05\n\n### Fixed\n\n- `test_piracy` no longer reports type piracy in the kwsorter, i.e. `kwcall` should no longer appear in the report. ([#171])\n\n\n## Version [v0.7.0] - 2023-08-29\n\n### Added\n\n- Installation and usage instructions to the documentation. ([#159])\n\n### Changed\n\n- `test_ambiguities` now allows to exclude non-singleton callables. Excluding a type means to exclude all methods of the callable (sometimes also called \"functor\") and the constructor. ([#144]) [BREAKING]\n- `test_piracy` considers more functions. Callables and qualified names are now also checked. ([#156]) [BREAKING]\n\n### Fixed\n\n- `test_ambiguities` prints less unnecessary whitespace. ([#158])\n- `test_ambiguities` no longer hangs indefinitely when there are many ambiguities. ([#166])\n\n\n## Version [v0.6.7] - 2023-09-19\n\n### Changed\n\n- `test_undefined_exports` additionally prints the modules of the undefined exports in the failure report. ([#177])\n\n### Fixed\n\n- `test_ambiguities` prints less unnecessary whitespace. ([#158])\n- Fix `test_piracy` for some methods with arguments of custom subtypes of `Function`. ([#170])\n\n\n## Version [v0.6.6] - 2023-08-24\n\n### Fixed\n\n- `test_ambiguities` no longer hangs indefinitely when there are many ambiguities. ([#166])\n\n\n## Version [v0.6.5] - 2023-06-26\n\n### Fixed\n\n- Typo when calling kwargs. ([#153])\n\n\n## Version [v0.6.4] - 2023-06-25\n\n### Added\n\n- `test_piracy` has a new kwarg `treat_as_own`. It is useful for testing packages that deliberately commit some type piracy, e.g. modules adding higher-level functionality to a lightweight C-wrapper, or packages that are extending `StatsAPI.jl`. ([#140])\n\n### Changed\n\n- Explanation of `test_unbound_args` in the docstring. ([#146])\n\n### Fixed\n\n- Callable objects with type parameters no longer error in `test_ambiguities`' kwarg `exclude`. ([#142])\n\n\n## Version [v0.6.3] - 2023-06-05\n\n### Changed\n\n- When installing a method for a union type, it is only reported by `test_piracy` if *all* types in the union are foreign (instead of *any* for arguments). ([#131])\n\n### Fixed\n\n- `test_deps_compat`'s kwarg `ignore` now works as intended. ([#130])\n- Weakdeps are not reported as stale by `test_stale_deps` anymore. ([#135])\n\n\n## Version [v0.6.2] - 2023-06-02\n\n### Added\n\n- `test_ambiguities`, `test_undefined_exports`, `test_piracy`, and `test_unbound_args` each have a new kwarg `broken` to mark the test as broken using `Test.@test_broken`. ([#124])\n\n### Changed\n\n- `test_piracy` now prints the offending methods in a more readable way. ([#93])\n- Extend `test_project_toml_formatting` to `docs/Project.toml`. ([#115])\n\n### Fixed\n\n- `test_stale_deps` no longer fails if any of the loaded packages prints during loading. ([#113])\n- Clarified the error message of `test_unbound_args`. ([#103])\n- Clarified the error message of `test_project_toml_formatting`. ([#122])\n\n\n<!-- Links generated by Changelog.jl -->\n\n[v0.6.2]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.2\n[v0.6.3]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.3\n[v0.6.4]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.4\n[v0.6.5]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.5\n[v0.6.6]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.6\n[v0.6.7]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.7\n[v0.7.0]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.0\n[v0.7.1]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.1\n[v0.7.2]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.2\n[v0.7.3]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.3\n[v0.7.4]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.4\n[v0.8.0]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.0\n[v0.8.1]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.1\n[v0.8.2]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.2\n[v0.8.3]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.3\n[v0.8.4]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.4\n[v0.8.5]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.5\n[v0.8.6]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.6\n[v0.8.7]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.7\n[v0.8.8]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.8\n[v0.8.9]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.9\n[v0.8.10]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.10\n[v0.8.11]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.11\n[v0.8.12]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.12\n[v0.8.13]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.13\n[v0.8.14]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.14\n[v1.0.0]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v1.0.0\n[#93]: https://github.com/JuliaTesting/Aqua.jl/issues/93\n[#103]: https://github.com/JuliaTesting/Aqua.jl/issues/103\n[#113]: https://github.com/JuliaTesting/Aqua.jl/issues/113\n[#115]: https://github.com/JuliaTesting/Aqua.jl/issues/115\n[#122]: https://github.com/JuliaTesting/Aqua.jl/issues/122\n[#124]: https://github.com/JuliaTesting/Aqua.jl/issues/124\n[#128]: https://github.com/JuliaTesting/Aqua.jl/issues/128\n[#130]: https://github.com/JuliaTesting/Aqua.jl/issues/130\n[#131]: https://github.com/JuliaTesting/Aqua.jl/issues/131\n[#135]: https://github.com/JuliaTesting/Aqua.jl/issues/135\n[#140]: https://github.com/JuliaTesting/Aqua.jl/issues/140\n[#142]: https://github.com/JuliaTesting/Aqua.jl/issues/142\n[#144]: https://github.com/JuliaTesting/Aqua.jl/issues/144\n[#146]: https://github.com/JuliaTesting/Aqua.jl/issues/146\n[#153]: https://github.com/JuliaTesting/Aqua.jl/issues/153\n[#156]: https://github.com/JuliaTesting/Aqua.jl/issues/156\n[#158]: https://github.com/JuliaTesting/Aqua.jl/issues/158\n[#159]: https://github.com/JuliaTesting/Aqua.jl/issues/159\n[#166]: https://github.com/JuliaTesting/Aqua.jl/issues/166\n[#170]: https://github.com/JuliaTesting/Aqua.jl/issues/170\n[#171]: https://github.com/JuliaTesting/Aqua.jl/issues/171\n[#174]: https://github.com/JuliaTesting/Aqua.jl/issues/174\n[#177]: https://github.com/JuliaTesting/Aqua.jl/issues/177\n[#188]: https://github.com/JuliaTesting/Aqua.jl/issues/188\n[#193]: https://github.com/JuliaTesting/Aqua.jl/issues/193\n[#200]: https://github.com/JuliaTesting/Aqua.jl/issues/200\n[#202]: https://github.com/JuliaTesting/Aqua.jl/issues/202\n[#203]: https://github.com/JuliaTesting/Aqua.jl/issues/203\n[#209]: https://github.com/JuliaTesting/Aqua.jl/issues/209\n[#215]: https://github.com/JuliaTesting/Aqua.jl/issues/215\n[#230]: https://github.com/JuliaTesting/Aqua.jl/issues/230\n[#233]: https://github.com/JuliaTesting/Aqua.jl/issues/233\n[#234]: https://github.com/JuliaTesting/Aqua.jl/issues/234\n[#236]: https://github.com/JuliaTesting/Aqua.jl/issues/236\n[#240]: https://github.com/JuliaTesting/Aqua.jl/issues/240\n[#241]: https://github.com/JuliaTesting/Aqua.jl/issues/241\n[#250]: https://github.com/JuliaTesting/Aqua.jl/issues/250\n[#255]: https://github.com/JuliaTesting/Aqua.jl/issues/255\n[#256]: https://github.com/JuliaTesting/Aqua.jl/issues/256\n[#272]: https://github.com/JuliaTesting/Aqua.jl/issues/272\n[#275]: https://github.com/JuliaTesting/Aqua.jl/issues/275\n[#277]: https://github.com/JuliaTesting/Aqua.jl/issues/277\n[#279]: https://github.com/JuliaTesting/Aqua.jl/issues/279\n[#281]: https://github.com/JuliaTesting/Aqua.jl/issues/281\n[#285]: https://github.com/JuliaTesting/Aqua.jl/issues/285\n[#287]: https://github.com/JuliaTesting/Aqua.jl/issues/287\n[#288]: https://github.com/JuliaTesting/Aqua.jl/issues/288\n[#297]: https://github.com/JuliaTesting/Aqua.jl/issues/297\n[#309]: https://github.com/JuliaTesting/Aqua.jl/issues/309\n[#313]: https://github.com/JuliaTesting/Aqua.jl/issues/313\n[#319]: https://github.com/JuliaTesting/Aqua.jl/issues/319\n[#322]: https://github.com/JuliaTesting/Aqua.jl/issues/322\n[#328]: https://github.com/JuliaTesting/Aqua.jl/issues/328\n[#334]: https://github.com/JuliaTesting/Aqua.jl/issues/334\n[#344]: https://github.com/JuliaTesting/Aqua.jl/issues/344\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2019 Takafumi Arakaki\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "JULIA:=julia\n\ndefault: help\n\n.PHONY: default changelog docs docs-instantiate generate_badge generate_favicon help test\n\ndocs-instantiate:\n\t${JULIA} docs/instantiate.jl\n\nchangelog: docs-instantiate\n\t${JULIA} --project=docs docs/changelog.jl\n\ndocs: docs-instantiate\n\t${JULIA} --project=docs docs/make.jl\n\ngenerate_badge:\n\tSVG_BASE64=$(shell base64 -w 0 docs/src/assets/logo.svg); \\\n\tcurl -o \"badge.svg\" \"https://img.shields.io/badge/tested_with-Aqua.jl-05C3DD.svg?logo=data:image/svg+xml;base64,$$SVG_BASE64\"\n\ngenerate_favicon:\n\tconvert -background none docs/src/assets/logo.svg -resize 256x256 -gravity center -extent 256x256 logo.png\n\tconvert logo.png -define icon:auto-resize=256,64,48,32,16 docs/src/assets/favicon.ico\n\trm logo.png\n\ntest:\n\t${JULIA} --project -e 'using Pkg; Pkg.test()'\n\nhelp:\n\t@echo \"The following make commands are available:\"\n\t@echo \" - make changelog: update all links in CHANGELOG.md's footer\"\n\t@echo \" - make docs: build the documentation\"\n\t@echo \" - make generate_badge: generate the Aqua.jl badge\"\n\t@echo \" - make generate_favicon: generate the Aqua.jl favicon\"\n\t@echo \" - make test: run the tests\"\n"
  },
  {
    "path": "Project.toml",
    "content": "name = \"Aqua\"\nuuid = \"4c88cf16-eb10-579e-8560-4a9242c79595\"\nauthors = [\"Takafumi Arakaki <aka.tkf@gmail.com> and contributors\"]\nversion = \"1.0.0-DEV\"\n\n[deps]\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nPrecompileTools = \"aea7be01-6a6a-4083-8856-8a6e6704d82a\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[compat]\nPkg = \"1.6\"\nPrecompileTools = \"1\"\nTest = \"<0.0.1, 1\"\njulia = \"1.6\"\n\n[extras]\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Test\"]\n"
  },
  {
    "path": "README.md",
    "content": "# Aqua.jl: *A*uto *QU*ality *A*ssurance for Julia packages\n\n[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliatesting.github.io/Aqua.jl/stable)\n[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliatesting.github.io/Aqua.jl/dev)\n[![GitHub Actions](https://github.com/JuliaTesting/Aqua.jl/workflows/Run%20tests/badge.svg)](https://github.com/JuliaTesting/Aqua.jl/actions?query=workflow%3ARun+tests)\n[![Codecov](https://codecov.io/gh/JuliaTesting/Aqua.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaTesting/Aqua.jl)\n[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)\n\nAqua.jl provides functions to run a few automatable checks for Julia packages:\n\n* There are no method ambiguities.\n* There are no undefined `export`s.\n* There are no unbound type parameters.\n* There are no stale dependencies listed in `Project.toml`.\n* Check that test target of the root project `Project.toml` and test project (`test/Project.toml`) are consistent.\n* Check that all external packages listed in `deps` have corresponding `compat` entries.\n* There are no \"obvious\" type piracies.\n* The package does not create any persistent Tasks that might block precompilation of dependencies.\n\nSee more in the [documentation](https://juliatesting.github.io/Aqua.jl/).\n\nFor a detailed list of changes please refer to the [changelog](CHANGELOG.md).\n\n## Setup\n\nPlease consult the [stable documentation](https://juliatesting.github.io/Aqua.jl/) and the the [dev documentation](https://juliatesting.github.io/Aqua.jl/dev/) for the latest instructions.\n\n## Badge\n\nYou can add the following line in README.md to include Aqua.jl badge:\n\n```markdown\n[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)\n```\n\nwhich is rendered as\n\n> [![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)"
  },
  {
    "path": "docs/Project.toml",
    "content": "[deps]\nChangelog = \"5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e\"\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\n\n[compat]\nChangelog = \"1\"\nDocumenter = \"1\"\n"
  },
  {
    "path": "docs/changelog.jl",
    "content": "using Changelog\n\nChangelog.generate(\n    Changelog.CommonMark(),\n    joinpath(@__DIR__, \"..\", \"CHANGELOG.md\");\n    repo = \"JuliaTesting/Aqua.jl\",\n)\n"
  },
  {
    "path": "docs/instantiate.jl",
    "content": "# This script can be used to quickly instantiate the docs/Project.toml environment.\nusing Pkg, TOML\n\npackage_directory = joinpath(@__DIR__, \"..\")\ndocs_directory = isempty(ARGS) ? @__DIR__() : joinpath(pwd(), ARGS[1])\ncd(docs_directory) do\n    Pkg.activate(docs_directory)\n    Pkg.develop(PackageSpec(path = package_directory))\n    Pkg.instantiate()\n\n    # Remove Aqua again from docs/Project.toml\n    lines = readlines(joinpath(docs_directory, \"Project.toml\"))\n    open(joinpath(docs_directory, \"Project.toml\"), \"w\") do io\n        for line in lines\n            if line == \"Aqua = \\\"4c88cf16-eb10-579e-8560-4a9242c79595\\\"\"\n                continue\n            end\n            println(io, line)\n        end\n    end\nend\n"
  },
  {
    "path": "docs/make.jl",
    "content": "using Documenter, Aqua, Changelog\n\n# Generate a Documenter-friendly changelog from CHANGELOG.md\nChangelog.generate(\n    Changelog.Documenter(),\n    joinpath(@__DIR__, \"..\", \"CHANGELOG.md\"),\n    joinpath(@__DIR__, \"src\", \"release-notes.md\");\n    repo = \"JuliaTesting/Aqua.jl\",\n)\n\nmakedocs(;\n    sitename = \"Aqua.jl\",\n    format = Documenter.HTML(;\n        repolink = \"https://github.com/JuliaTesting/Aqua.jl\",\n        assets = [\"assets/favicon.ico\"],\n        size_threshold_ignore = [\"release-notes.md\"],\n    ),\n    authors = \"Takafumi Arakaki\",\n    modules = [Aqua],\n    pages = [\n        \"Home\" => \"index.md\",\n        \"Tests\" => [\n            \"test_all.md\",\n            \"ambiguities.md\",\n            \"unbound_args.md\",\n            \"exports.md\",\n            \"project_extras.md\",\n            \"stale_deps.md\",\n            \"deps_compat.md\",\n            \"piracies.md\",\n            \"persistent_tasks.md\",\n            \"undocumented_names.md\",\n        ],\n        \"release-notes.md\",\n    ],\n)\n\ndeploydocs(; repo = \"github.com/JuliaTesting/Aqua.jl\", push_preview = true)\n"
  },
  {
    "path": "docs/src/ambiguities.md",
    "content": "# Ambiguities\n\nMethod ambiguities are cases where multiple methods are applicable to a given set of arguments, without having a most specific method.\n\n## Examples\nOne easy example is the following:\n```@repl\nf(x::Int, y::Integer) = 1\nf(x::Integer, y::Int) = 2\n\nprintln(f(1, 2))\n```\nThis will throw an `MethodError` because both methods are equally specific. The solution is to add a third method:\n```julia\nf(x::Int, y::Int) = ? # `?` is dependent on the use case, most times it will be `1` or `2`\n```\n\n## [Test function](@id test_ambiguities)\n\n```@docs\nAqua.test_ambiguities\n```\n"
  },
  {
    "path": "docs/src/deps_compat.md",
    "content": "# Compat entries\n\nIn your `Project.toml` you can (and should) use compat entries to specify\nwith which versions of Julia and your dependencies your package is compatible with.\nThis is important to ease the installation and upgrade of your package for users,\nand to keep everything working in the case of breaking changes in Julia or your dependencies.\n\nFor more details, see the [Pkg docs](https://julialang.github.io/Pkg.jl/v1/compatibility/).\n\n## [Test function](@id test_deps_compat)\n\n```@docs\nAqua.test_deps_compat\n```\n"
  },
  {
    "path": "docs/src/exports.md",
    "content": "# Undefined exports\n\n## [Test function](@id test_undefined_exports)\n\n```@docs\nAqua.test_undefined_exports\n```\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "# Aqua.jl: *A*uto *QU*ality *A*ssurance for Julia packages\n\nAqua.jl provides functions to run a few automatable checks for Julia packages:\n\n* There are no method ambiguities.\n* There are no undefined `export`s.\n* There are no unbound type parameters.\n* There are no stale dependencies listed in `Project.toml`.\n* Check that test target of the root project `Project.toml` and test project (`test/Project.toml`) are consistent.\n* Check that all external packages listed in `deps` have corresponding `compat` entries.\n* There are no \"obvious\" type piracies.\n* The package does not create any persistent Tasks that might block precompilation of dependencies.\n\n## Quick usage\n\nCall `Aqua.test_all(YourPackage)` from the REPL, e.g.,\n\n```julia\nusing YourPackage\nusing Aqua\nAqua.test_all(YourPackage)\n```\n\n## How to add Aqua.jl...\n\n### ...as a test dependency?\n\nThere are two ways to add Aqua.jl as a test dependency to your package.\nTo avoid breaking tests when a new Aqua.jl version is released, it is\nrecommended to add a version bound for Aqua.jl.\n\n 1. In `YourPackage/test/Project.toml`, add Aqua.jl to `[dep]` and `[compat]` sections, like\n    ```toml\n    [deps]\n    Aqua = \"4c88cf16-eb10-579e-8560-4a9242c79595\"\n    Test = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n    [compat]\n    Aqua = \"0.8\"\n    ```\n\n 2. In `YourPackage/Project.toml`, add Aqua.jl to `[compat]` and `[extras]` section and the `test` target, like\n    ```toml\n    [compat]\n    Aqua = \"0.8\"\n\n    [extras]\n    Aqua = \"4c88cf16-eb10-579e-8560-4a9242c79595\"\n    Test = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n    [targets]\n    test = [\"Aqua\", \"Test\"]\n    ```\n\nIf your package supports Julia pre-1.2, you need to use the second approach,\nalthough you can use both approaches at the same time.\n\n!!! warning\n    In normal use, `Aqua.jl` should not be added to `[deps]` in `YourPackage/Project.toml`!\n\n### ...to your tests?\nIt is recommended to create a separate file `YourPackage/test/Aqua.jl` that gets included in `YourPackage/test/runtests.jl`\nwith either\n\n```julia\nusing Aqua\nAqua.test_all(YourPackage)\n```\nor some fine-grained checks with options, e.g.,\n\n```julia\nusing Aqua\n\n@testset \"Aqua.jl\" begin\n  Aqua.test_all(\n    YourPackage;\n    ambiguities=(exclude=[SomePackage.some_function], broken=true),\n    stale_deps=(ignore=[:SomePackage],),\n    deps_compat=(ignore=[:SomeOtherPackage],),\n    piracies=false,\n  )\nend\n```\nNote, that for all tests with no explicit options provided, the default options are used.\n\nFor more details on the options, see the respective functions [here](@ref test_all).\n\n## Examples\nThe following is a small selection of packages that use Aqua.jl:\n- [GAP.jl](https://github.com/oscar-system/GAP.jl)\n- [Hecke.jl](https://github.com/thofma/Hecke.jl)\n- [Oscar.jl](https://github.com/oscar-system/Oscar.jl)\n\n## Badge\n\nYou can add the following line in README.md to include Aqua.jl badge:\n\n```markdown\n[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)\n```\n\nwhich is rendered as\n\n[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)\n\n"
  },
  {
    "path": "docs/src/persistent_tasks.md",
    "content": "# Persistent Tasks\n\n## Motivation\n\nJulia 1.10 and higher wait for all running `Task`s to finish\nbefore writing out the precompiled (cached) version of the package.\nOne consequence is that a package that launches\n`Task`s in its `__init__` function may precompile successfully,\nbut block precompilation of any packages that depend on it.\n\nThe symptom of this problem is a message\n```\n◐ MyPackage: Waiting for background task / IO / timer. Interrupt to inspect...\n```\nthat may appear during precompilation, with that precompilation process\n\"hanging\" until you press Ctrl-C.\n\nAqua has checks to determine whether your package *causes* this problem.\nConversely, if you're a *victim* of this problem, it also has tools to help you\ndetermine which of your dependencies is causing the problem.\n\n## Example\n\nLet's create a dummy package, `PkgA`, that launches a persistent `Task`:\n\n```julia\nmodule PkgA\nconst t = Ref{Timer}()   # used to prevent the Timer from being garbage-collected\n__init__() = t[] = Timer(0.1; interval=1)   # create a persistent `Timer` `Task`\nend\n```\n\n`PkgA` will precompile successfully, because `PkgA.__init__()` does not\nrun when `PkgA` is precompiled. However,\n\n```julia\nmodule PkgB\nusing PkgA\nend\n```\n\nfails to precompile: `using PkgA` runs `PkgA.__init__()`, which\nleaves the `Timer` `Task` running, and that causes precompilation\nof `PkgB` to hang.\n\nWithout Aqua's tests, the developers of `PkgA` might not realize that their\npackage is essentially unusable with any other package.\n\n## Checking for persistent tasks\n\nRunning all of Aqua's tests will automatically check whether your package falls\ninto this trap. In addition, there are ways to manually run (or tweak) this\nspecific test.\n\n### Manually running the persistent-tasks check\n\n[`Aqua.test_persistent_tasks(MyPackage)`](@ref) will check whether `MyPackage` blocks\nprecompilation for any packages that depend on it.\n\n### Using an `expr` to check more than just `__init__`\n\nBy default, `Aqua.test_persistent_tasks` only checks whether a package's\n`__init__` function leaves persistent tasks running. To check whether other\npackage functions leave persistent tasks running, pass a quoted expression:\n\n```julia\nAqua.test_persistent_tasks(MyPackage; expr = quote\n    # Code to run after loading MyPackage\n    server = MyPackage.start_server()\n    MyPackage.stop_server!(server)  # ideally, this this should cleanly shut everything down. Does it?\nend)\n```\n\nHere is an example test with a dummy `expr` which will obviously fail, because it's explicitly\nspawning a Task that never dies.\n```@repl\nusing Aqua\nAqua.test_persistent_tasks(Aqua, expr = quote\n    Threads.@spawn while true sleep(0.5) end\nend)\n```\n\n## How the test works\n\nThis test works by launching a Julia process that tries to precompile a\ndummy package similar to `PkgB` above, modified to signal back to Aqua when\n`PkgA` has finished loading. The test fails if the gap between loading `PkgA`\nand finishing precompilation exceeds time `tmax`.\n\n## How to fix failing packages\n\nOften, the easiest fix is to modify the `__init__` function to check whether the\nJulia process is precompiling some other package; if so, don't launch the\npersistent `Task`s.\n\n```julia\nfunction __init__()\n    # Other setup code here\n    if ccall(:jl_generating_output, Cint, ()) == 0   # if we're not precompiling...\n        # launch persistent tasks here\n    end\nend\n```\n\nIn more complex cases, you may need to modify the task to support a clean\nshutdown. For example, if you have a `Task` that runs a never-terminating\n`while` loop, you could change\n\n```\n    while true\n        ⋮\n    end\n```\n\nto\n\n```\n    while task_should_run[]\n        ⋮\n    end\n```\n\nwhere\n\n```\nconst task_should_run = Ref(true)\n```\n\nis a global constant in your module. Setting `task_should_run[] = false` from\noutside that `while` loop will cause it to terminate on its next iteration,\nallowing the `Task` to finish.\n\n## Additional information\n\n[Julia's devdocs](https://docs.julialang.org/en/v1/devdocs/precompile_hang/)\nalso discuss this issue.\n\n## [Test functions](@id test_persistent_tasks)\n\n```@docs\nAqua.test_persistent_tasks\nAqua.find_persistent_tasks_deps\n```\n"
  },
  {
    "path": "docs/src/piracies.md",
    "content": "# Type piracy\n\nType piracy is a term used to describe adding methods to a foreign function\nwith only foreign arguments.\nThis is considered bad practice because it can cause unexpected behavior\nwhen the function is called, in particular, it can change the behavior of\none of your dependencies depending on if your package is loaded or not.\nThis makes it hard to reason about the behavior of your code, and may\nintroduce bugs that are hard to track down.\n\nSee [Julia documentation](https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-type-piracy) for more information about type piracy.\n\n## Examples\n\nSay that `PkgA` is foreign, and let's look at the different ways that `PkgB` extends its function `bar`.\n\n```julia\nmodule PkgA\n    struct C end\n    bar(x::C) = 42\n    bar(x::Vector) = 43\nend\n\nmodule PkgB \n    import PkgA: bar, C\n    struct D end\n    bar(x::C) = 1\n    bar(xs::D...) = 2\n    bar(x::Vector{<:D}) = 3\n    bar(x::Vector{D}) = 4 # slightly bad (may cause invalidations)\n    bar(x::Union{C,D}) = 5 # slightly bad (a change in PkgA may turn it into piracy)\n    #                        (for example changing bar(x::C) = 1 to bar(x::Union{C,Int}) = 1)\nend\n```\n\nThe following cases are enumerated by the return values in the example above:\n1. This is the worst case of type piracy. The value of `bar(C())` can be\n   either `1` or `42` and will depend on whether `PkgB` is loaded or not.\n2. This is also a bad case of type piracy. `bar()` throws a `MethodError` with\n   only `PkgA` available, and returns `2` with `PkgB` loaded. `PkgA` may add\n   a method for `bar()` that takes no arguments in the future, and then this\n   is equivalent to case 1.\n3. This is a moderately bad case of type piracy. `bar(Union{}[])` returns `3`\n   when `PkgB` is loaded, and `43` when `PkgB` is not loaded, although neither\n   of the occurring types are defined in `PkgB`. This case is not as bad as\n   cases 1 and 2, because it is only about behavior around `Union{}`, which has\n   no instances.\n4. Depending on ones understanding of type piracy, this could be considered piracy\n   as well. In particular, this may cause invalidations.\n5. This is a slightly bad case of type piracy. In the current form, `bar(C())`\n   returns `42` as the dispatch on `Union{C,D}` is less specific. However, a\n   future change in `PkgA` may change this behavior, e.g. by changing `bar(x::C)`\n   to `bar(x::Union{C,Int})` the call `bar(C())` would become ambiguous.\n\n!!! note\n    The test function below currently only checks for cases 1 and 2.\n\n## [Test function](@id test_piracies)\n\n```@docs\nAqua.test_piracies\n```\n"
  },
  {
    "path": "docs/src/project_extras.md",
    "content": "# Project.toml extras\n\nThere are two different ways to specify test-only dependencies (see [the Pkg docs](https://julialang.github.io/Pkg.jl/v1/creating-packages/#Test-specific-dependencies)):\n1. Add the test-only dependencies to the `[extras]` section of your `Project.toml` file\n   and use a test target.\n2. Add the test-only dependencies to the `[deps]` section of your `test/Project.toml` file.\n   This is only available in Julia 1.2 and later.\n\nThis test checks checks that, in case you use both methods, the set of test-only dependencies\nis the same in both ways.\n\n## [Test function](@id test_project_extras)\n\n```@docs\nAqua.test_project_extras\n```\n"
  },
  {
    "path": "docs/src/stale_deps.md",
    "content": "# Stale dependencies\n\n## [Test function](@id test_stale_deps)\n\n```@docs\nAqua.test_stale_deps\n```\n"
  },
  {
    "path": "docs/src/test_all.md",
    "content": "# [Test everything](@id test_all)\n\nThis test runs most of the other tests in this module.\nThe defaults should be fine for most packages.\nIf you have a package that needs to customize the test, you can do so by providing appropriate keyword arguments to `Aqua.test_all()` (see below)\n\n```@docs\nAqua.test_all\n```\n"
  },
  {
    "path": "docs/src/unbound_args.md",
    "content": "# Unbound Type Parameters\n\nAn unbound type parameter is a type parameter with a `where`,\nthat does not occur in the signature of some dispatch of the method.\n\n## Examples\n\nThe following methods each have `T` as an unbound type parameter:\n\n```@repl unbound\nf(x::Int) where {T} = do_something(x)\ng(x::T...) where {T} = println(T)\n```\n\nIn the cases of `f` above, the unbound type parameter `T` is neither\npresent in the signature of the methods nor as a bound of another type parameter.\nHere, the type parameter `T` can be removed without changing any semantics.\n\nFor signatures with `Vararg` (cf. `g` above), the type parameter is unbound for the \nzero-argument case (e.g. `g()`).\n\n```@repl unbound\ng(1.0, 2.0)\ng(1)\ng()\n```\n\nA possible fix would be to replace `g` by two methods.\n\n```@repl unbound2\ng() = println(Int)  # Defaults to `Int`\ng(x1::T, x2::T...) where {T} = println(T)\ng(1.0, 2.0)\ng(1)\ng()\n```\n\n## [Test function](@id test_unbound_args)\n\n```@docs\nAqua.test_unbound_args\n```\n"
  },
  {
    "path": "docs/src/undocumented_names.md",
    "content": "# Undocumented names\n\n## [Test function](@id test_undocumented_names)\n\n```@docs\nAqua.test_undocumented_names\n```\n"
  },
  {
    "path": "src/Aqua.jl",
    "content": "module Aqua\n\nusing Base: Docs, PkgId, UUID\nusing Pkg: Pkg, TOML, PackageSpec\nusing Pkg.Types: VersionSpec, semver_spec\nusing Test\n\n\ninclude(\"utils.jl\")\ninclude(\"ambiguities.jl\")\ninclude(\"unbound_args.jl\")\ninclude(\"exports.jl\")\ninclude(\"project_extras.jl\")\ninclude(\"stale_deps.jl\")\ninclude(\"deps_compat.jl\")\ninclude(\"piracies.jl\")\ninclude(\"persistent_tasks.jl\")\ninclude(\"undocumented_names.jl\")\n\n\"\"\"\n    test_all(testtarget::Module)\n\nRun the following tests on the module `testtarget`:\n\n* [`test_ambiguities([testtarget])`](@ref test_ambiguities)\n* [`test_unbound_args(testtarget)`](@ref test_unbound_args)\n* [`test_undefined_exports(testtarget)`](@ref test_undefined_exports)\n* [`test_project_extras(testtarget)`](@ref test_project_extras)\n* [`test_stale_deps(testtarget)`](@ref test_stale_deps)\n* [`test_deps_compat(testtarget)`](@ref test_deps_compat)\n* [`test_piracies(testtarget)`](@ref test_piracies)\n* [`test_persistent_tasks(testtarget)`](@ref test_persistent_tasks)\n* [`test_undocumented_names(testtarget)`](@ref test_undocumented_names)\n\nThe keyword argument `\\$x` (e.g., `ambiguities`) can be used to\ncontrol whether or not to run `test_\\$x` (e.g., `test_ambiguities`).\nIf `test_\\$x` supports keyword arguments, a `NamedTuple` can also be\npassed to `\\$x` to specify the keyword arguments for `test_\\$x`.\n\n# Keyword Arguments\n- `ambiguities = true`\n- `unbound_args = true`\n- `undefined_exports = true`\n- `project_extras = true`\n- `stale_deps = true`\n- `deps_compat = true`\n- `piracies = true`\n- `persistent_tasks = true`\n- `undocumented_names = false`\n\"\"\"\nfunction test_all(\n    testtarget::Module;\n    ambiguities = true,\n    unbound_args = true,\n    undefined_exports = true,\n    project_extras = true,\n    stale_deps = true,\n    deps_compat = true,\n    piracies = true,\n    persistent_tasks = true,\n    undocumented_names = false,\n)\n    if ambiguities !== false\n        @testset \"Method ambiguity\" begin\n            test_ambiguities([testtarget]; askwargs(ambiguities)...)\n        end\n    end\n    if unbound_args !== false\n        @testset \"Unbound type parameters\" begin\n            test_unbound_args(testtarget; askwargs(unbound_args)...)\n        end\n    end\n    if undefined_exports !== false\n        @testset \"Undefined exports\" begin\n            test_undefined_exports(testtarget; askwargs(undefined_exports)...)\n        end\n    end\n    if project_extras !== false\n        @testset \"Compare Project.toml and test/Project.toml\" begin\n            isempty(askwargs(project_extras)) || error(\"Keyword arguments not supported\")\n            test_project_extras(testtarget)\n        end\n    end\n    if stale_deps !== false\n        @testset \"Stale dependencies\" begin\n            test_stale_deps(testtarget; askwargs(stale_deps)...)\n        end\n    end\n    if deps_compat !== false\n        @testset \"Compat bounds\" begin\n            test_deps_compat(testtarget; askwargs(deps_compat)...)\n        end\n    end\n    if piracies !== false\n        @testset \"Piracy\" begin\n            test_piracies(testtarget; askwargs(piracies)...)\n        end\n    end\n    if persistent_tasks !== false\n        @testset \"Persistent tasks\" begin\n            test_persistent_tasks(testtarget; askwargs(persistent_tasks)...)\n        end\n    end\n    if undocumented_names !== false\n        @testset \"Undocumented names\" begin\n            isempty(askwargs(undocumented_names)) ||\n                error(\"Keyword arguments not supported\")\n            test_undocumented_names(testtarget; askwargs(undocumented_names)...)\n        end\n    end\nend\n\ninclude(\"precompile.jl\")\n\nend # module\n"
  },
  {
    "path": "src/ambiguities.jl",
    "content": "\"\"\"\n    test_ambiguities(package::Union{Module, PkgId})\n    test_ambiguities(packages::Vector{Union{Module, PkgId}})\n\nTest that there is no method ambiguities in given package(s).\n\nIt calls `Test.detect_ambiguities` in a separated clean process to avoid\nfalse-positives.\n\n# Keyword Arguments\n- `broken::Bool = false`: If true, it uses `@test_broken` instead of\n  `@test` and shortens the error message.\n- `color::Union{Bool, Nothing} = nothing`: Enable/disable colorful\n  output if a `Bool`.  `nothing` (default) means to inherit the\n  setting in the current process.\n- `exclude::AbstractVector = []`: A vector of functions or types to be\n  excluded from ambiguity testing.  A function means to exclude _all_\n  its methods.  A type means to exclude _all_ its methods of the\n  callable (sometimes also called \"functor\") and the constructor.\n  That is to say, `MyModule.MyType` means to ignore ambiguities between\n  `(::MyType)(x, y::Int)` and `(::MyType)(x::Int, y)`.\n- `recursive::Bool = true`: Passed to `Test.detect_ambiguities`.\n  Note that the default here (`true`) is different from\n  `detect_ambiguities`.  This is for testing ambiguities in methods\n  defined in all sub-modules.\n- Other keyword arguments such as `imported` and `ambiguous_bottom`\n  are passed to `Test.detect_ambiguities` as-is.\n\"\"\"\ntest_ambiguities(packages; kwargs...) = _test_ambiguities(aspkgids(packages); kwargs...)\n\nconst ExcludeSpec = Pair{Base.PkgId,String}\n\nrootmodule(x::Type) = rootmodule(parentmodule(x))\nrootmodule(m::Module) = Base.require(PkgId(m))  # this handles Base/Core well\n\n# get the Type associated with x\nnormalize_exclude_obj(@nospecialize x) = x isa Type ? x : typeof(x)\n\nfunction normalize_exclude(@nospecialize x)\n    x = normalize_exclude_obj(x)\n    return Base.PkgId(rootmodule(x)) => join((fullname(parentmodule(x))..., string(nameof(x))), \".\")\nend\n\nfunction getexclude((pkgid, name)::ExcludeSpec)\n    nameparts = Symbol.(split(name, \".\"))\n    m = Base.require(pkgid)\n    for name in nameparts\n        m = getproperty(m, name)\n    end\n    return normalize_exclude_obj(m)\nend\n\nfunction normalize_and_check_exclude(exclude::AbstractVector)\n    exspecs = ExcludeSpec[normalize_exclude(exspec) for exspec in exclude]\n    for (i, exspec) in enumerate(exspecs)\n        if getexclude(exspec) != normalize_exclude_obj(exclude[i])\n            error(\"Name `$(exspec[2])` is resolved to a different object.\")\n        end\n    end\n    return exspecs::Vector{ExcludeSpec}\nend\n\nfunction reprexclude(exspecs::Vector{ExcludeSpec})\n    itemreprs = map(exspecs) do (pkgid, name)\n        string(\"(\", reprpkgid(pkgid), \" => \", repr(name), \")\")\n    end\n    return string(\"Aqua.ExcludeSpec[\", join(itemreprs, \", \"), \"]\")\nend\n\nfunction _test_ambiguities(packages::Vector{PkgId}; broken::Bool = false, kwargs...)\n    num_ambiguities, strout, strerr =\n        _find_ambiguities(packages; skipdetails = broken, kwargs...)\n\n    print(stderr, strerr)\n    print(stdout, strout)\n\n    if broken\n        @test_broken iszero(num_ambiguities)\n    else\n        @test iszero(num_ambiguities)\n    end\nend\n\nfunction _find_ambiguities(\n    packages::Vector{PkgId};\n    skipdetails::Bool = false,\n    color::Union{Bool,Nothing} = nothing,\n    exclude::AbstractVector = [],\n    # Options to be passed to `Test.detect_ambiguities`:\n    detect_ambiguities_options...,\n)\n    packages_repr = reprpkgids(collect(packages))\n    options_repr = checked_repr((; recursive = true, detect_ambiguities_options...))\n    exclude_repr = reprexclude(normalize_and_check_exclude(exclude))\n\n    # Ambiguity test is run inside a clean process.\n    # https://github.com/JuliaLang/julia/issues/28804\n    code = \"\"\"\n    $(Base.load_path_setup_code())\n    using Aqua\n    Aqua.test_ambiguities_impl(\n        $packages_repr,\n        $options_repr,\n        $exclude_repr,\n        $skipdetails,\n    ) || exit(1)\n    \"\"\"\n    cmd = Base.julia_cmd()\n    if something(color, Base.JLOptions().color == 1)\n        cmd = `$cmd --color=yes`\n    end\n    cmd = `$cmd --startup-file=no -e $code`\n\n    mktemp() do outfile, out\n        mktemp() do errfile, err\n            succ = success(pipeline(cmd; stdout = out, stderr = err))\n            strout = read(outfile, String)\n            strerr = read(errfile, String)\n            num_ambiguities = if succ\n                0\n            else\n                reg_match = match(r\"(\\d+) ambiguities found\", strerr)\n\n                reg_match === nothing && error(\n                    \"Failed to parse output of `detect_ambiguities`.\\nThe stdout was:\\n\" *\n                    strout *\n                    \"\\n\\nThe stderr was:\\n\" *\n                    strerr,\n                )\n\n                parse(Int, reg_match.captures[1]::AbstractString)\n            end\n            return num_ambiguities, strout, strerr\n        end\n    end\nend\n\nfunction reprpkgids(packages::Vector{PkgId})\n    packages_repr = sprint() do io\n        println(io, '[')\n        for pkg in packages\n            println(io, reprpkgid(pkg))\n        end\n        println(io, ']')\n    end\n    @assert Base.eval(Main, Meta.parse(packages_repr)) == packages\n    return packages_repr\nend\n\nfunction reprpkgid(pkg::PkgId)\n    name = pkg.name\n    uuid = pkg.uuid\n    if uuid === nothing\n        return \"Base.PkgId($(repr(name)))\"\n    end\n    return \"Base.PkgId(Base.UUID($(repr(uuid.value))), $(repr(name)))\"\nend\n\n# try to extract the called function, or nothing if it is hard to analyze\nfunction trygetft(m::Method)\n    sig = Base.unwrap_unionall(m.sig)::DataType\n    ft = sig.parameters[is_kwcall(sig) ? 3 : 1]\n    ft = Base.unwrap_unionall(ft)\n    if ft isa DataType && ft.name === Type.body.name\n        ft = Base.unwrap_unionall(ft.parameters[1])\n    end\n    if ft isa DataType\n        return ft.name.wrapper\n    end\n    return nothing # cannot exclude signatures with Union\nend\n\nfunction test_ambiguities_impl(\n    packages::Vector{PkgId},\n    options::NamedTuple,\n    exspecs::Vector{ExcludeSpec},\n    skipdetails::Bool,\n)\n    modules = map(Base.require, packages)\n    @debug \"Testing method ambiguities\" modules\n    ambiguities = detect_ambiguities(modules...; options...)\n\n    if !isempty(exspecs)\n        exclude_ft = Any[getexclude(spec) for spec in exspecs] # vector of Type objects\n        ambiguities = filter(ambiguities) do (m1, m2)\n            trygetft(m1) ∉ exclude_ft && trygetft(m2) ∉ exclude_ft\n        end\n    end\n\n    sort!(ambiguities, by = (ms -> (ms[1].name, ms[2].name)))\n\n    if !isempty(ambiguities)\n        printstyled(\n            stderr,\n            \"$(length(ambiguities)) ambiguities found. To get a list, set `broken = false`.\\n\";\n            bold = true,\n            color = Base.error_color(),\n        )\n    end\n    if !skipdetails\n        for (i, (m1, m2)) in enumerate(ambiguities)\n            println(stderr, \"Ambiguity #\", i)\n            println(stderr, m1)\n            println(stderr, m2)\n            @static if isdefined(Base, :morespecific)\n                ambiguity_hint(stderr, m1, m2)\n                println(stderr)\n            end\n            println(stderr)\n        end\n    end\n    return isempty(ambiguities)\nend\n\nfunction ambiguity_hint(io::IO, m1::Method, m2::Method)\n    # based on base/errorshow.jl#showerror_ambiguous\n    # https://github.com/JuliaLang/julia/blob/v1.7.2/base/errorshow.jl#L327-L353\n    sigfix = Any\n    sigfix = typeintersect(m1.sig, sigfix)\n    sigfix = typeintersect(m2.sig, sigfix)\n    if isa(Base.unwrap_unionall(sigfix), DataType) && sigfix <: Tuple\n        let sigfix = sigfix\n            if all(m -> Base.morespecific(sigfix, m.sig), [m1, m2])\n                print(io, \"\\nPossible fix, define\\n  \")\n                # Use `invokelatest` to not throw because of world age problems due to new types.\n                invokelatest(Base.show_tuple_as_call, io, :function, sigfix)\n            else\n                println(io)\n                print(\n                    io,\n                    \"\"\"To resolve the ambiguity, try making one of the methods more specific, or \n                    adding a new method more specific than any of the existing applicable methods.\"\"\",\n                )\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "src/deps_compat.jl",
    "content": "\"\"\"\n    Aqua.test_deps_compat(package)\n\nTest that the `Project.toml` of `package` has a `compat` entry for\neach package listed under `deps` and for `julia`.\n\n# Arguments\n- `packages`: a top-level `Module`, a `Base.PkgId`, or a collection of\n  them.\n\n# Keyword Arguments\n## Test choosers\n- `check_julia = true`: If true, additionally check for a compat entry for \"julia\".\n- `check_extras = true`: If true, additionally check \"extras\". A NamedTuple\n  can be used to pass keyword arguments with test options (see below).\n- `check_weakdeps = true`: If true, additionally check \"weakdeps\". A NamedTuple\n  can be used to pass keyword arguments with test options (see below).\n\n## Test options\nIf these keyword arguments are set directly, they only apply to the standard test\nfor \"deps\". To apply them to \"extras\" and \"weakdeps\", pass them as a NamedTuple\nto the corresponding `check_\\$x` keyword argument.\n- `broken::Bool = false`: If true, it uses `@test_broken` instead of\n  `@test` for \"deps\".\n- `ignore::Vector{Symbol}`: names of dependent packages to be ignored.\n\"\"\"\nfunction test_deps_compat(\n    pkg::PkgId;\n    check_julia = true,\n    check_extras = true,\n    check_weakdeps = true,\n    kwargs...,\n)\n    if check_julia !== false\n        @testset \"julia\" begin\n            isempty(askwargs(check_julia)) || error(\"Keyword arguments not supported\")\n            test_julia_compat(pkg)\n        end\n    end\n    @testset \"$pkg deps\" begin\n        test_deps_compat(pkg, \"deps\"; kwargs...)\n    end\n    if check_extras !== false\n        @testset \"$pkg extras\" begin\n            test_deps_compat(pkg, \"extras\"; askwargs(check_extras)...)\n        end\n    end\n    if check_weakdeps !== false\n        @testset \"$pkg weakdeps\" begin\n            test_deps_compat(pkg, \"weakdeps\"; askwargs(check_weakdeps)...)\n        end\n    end\nend\n\nfunction test_deps_compat(pkg::PkgId, deps_type::String; broken::Bool = false, kwargs...)\n    result = find_missing_deps_compat(pkg, deps_type; kwargs...)\n    if broken\n        @test_broken isempty(result)\n    else\n        @test isempty(result)\n    end\nend\n\n# Remove with next breaking version\nfunction test_deps_compat(packages::Vector{<:Union{Module,PkgId}}; kwargs...)\n    @testset \"$pkg\" for pkg in packages\n        test_deps_compat(pkg; kwargs...)\n    end\nend\n\nfunction test_deps_compat(mod::Module; kwargs...)\n    test_deps_compat(aspkgid(mod); kwargs...)\nend\n\nfunction test_julia_compat(pkg::PkgId; broken::Bool = false)\n    if broken\n        @test_broken has_julia_compat(pkg)\n    else\n        @test has_julia_compat(pkg)\n    end\nend\n\nfunction has_julia_compat(pkg::PkgId)\n    root_project_path, found = root_project_toml(pkg)\n    found || error(\"Unable to locate Project.toml\")\n    prj = TOML.parsefile(root_project_path)\n    return has_julia_compat(prj)\nend\n\nfunction has_julia_compat(prj::Dict{String,Any})\n    return \"julia\" in keys(get(prj, \"compat\", Dict{String,Any}()))\nend\n\nfunction find_missing_deps_compat(pkg::PkgId, deps_type::String = \"deps\"; kwargs...)\n    root_project_path, found = root_project_toml(pkg)\n    found || error(\"Unable to locate Project.toml\")\n    missing_compat =\n        find_missing_deps_compat(TOML.parsefile(root_project_path), deps_type; kwargs...)\n\n    if !isempty(missing_compat)\n        printstyled(\n            stderr,\n            \"$pkg does not declare a compat entry for the following $deps_type:\\n\";\n            bold = true,\n            color = Base.error_color(),\n        )\n        show(stderr, MIME\"text/plain\"(), missing_compat)\n        println(stderr)\n    end\n\n    return missing_compat\nend\n\nfunction find_missing_deps_compat(\n    prj::Dict{String,Any},\n    deps_type::String;\n    ignore::AbstractVector{Symbol} = Symbol[],\n)\n    deps = get(prj, deps_type, Dict{String,Any}())\n    compat = get(prj, \"compat\", Dict{String,Any}())\n\n    missing_compat = sort!(\n        PkgId[\n            d for d in map(d -> PkgId(UUID(last(d)), first(d)), collect(deps)) if\n            !(d.name in keys(compat)) && !(d.name in String.(ignore))\n        ];\n        by = (pkg -> pkg.name),\n    )\n    return missing_compat\nend\n"
  },
  {
    "path": "src/exports.jl",
    "content": "# avoid Base.isbindingresolved deprecation in https://github.com/JuliaLang/julia/pull/57253\nfunction isbindingresolved(m::Module, s::Symbol)\n    @static if VERSION >= v\"1.12.0-\"\n        return true\n    else\n        return Base.isbindingresolved(m, s)\n    end\nend\n\nfunction walkmodules(f, x::Module)\n    f(x)\n    for n in names(x; all = true)\n        # `isdefined` and `getproperty` can trigger deprecation warnings\n        if isbindingresolved(x, n) && !Base.isdeprecated(x, n)\n            isdefined(x, n) || continue\n            y = getproperty(x, n)\n            if y isa Module && y !== x && parentmodule(y) === x\n                walkmodules(f, y)\n            end\n        end\n    end\nend\n\nfunction undefined_exports(m::Module)\n    undefined = Symbol[]\n    walkmodules(m) do x\n        for n in names(x)\n            isdefined(x, n) || push!(undefined, Symbol(join([fullname(x)...; n], '.')))\n        end\n    end\n    return undefined\nend\n\n\"\"\"\n    test_undefined_exports(m::Module; broken::Bool = false)\n\nTest that all `export`ed names in `m` actually exist.\n\n# Keyword Arguments\n- `broken`: If true, it uses `@test_broken` instead of\n  `@test` and shortens the error message.\n\"\"\"\nfunction test_undefined_exports(m::Module; broken::Bool = false)\n    exports = undefined_exports(m)\n    if broken\n        if !isempty(exports)\n            printstyled(\n                stderr,\n                \"$(length(exports)) undefined exports detected. To get a list, set `broken = false`.\\n\";\n                bold = true,\n                color = Base.error_color(),\n            )\n        end\n        @test_broken isempty(exports)\n    else\n        if !isempty(exports)\n            printstyled(\n                stderr,\n                \"Undefined exports detected:\\n\";\n                bold = true,\n                color = Base.error_color(),\n            )\n            show(stderr, MIME\"text/plain\"(), exports)\n            println(stderr)\n        end\n        @test isempty(exports)\n    end\nend\n"
  },
  {
    "path": "src/persistent_tasks.jl",
    "content": "\"\"\"\n    Aqua.test_persistent_tasks(package)\n\nTest whether loading `package` creates persistent `Task`s\nwhich may block precompilation of dependent packages.\n\nSee also [`Aqua.find_persistent_tasks_deps`](@ref).\n\nIf you provide an optional `expr`, this tests whether loading `package` and running `expr`\ncreates persistent `Task`s. For example, you might start and shutdown a web server, and\nthis will test that there aren't any persistent `Task`s.\n\nOn Julia version 1.9 and before, this test always succeeds.\n\n# Arguments\n- `package`: a top-level `Module` or `Base.PkgId`.\n\n# Keyword Arguments\n- `broken::Bool = false`: If true, it uses `@test_broken` instead of\n  `@test`.\n- `tmax::Real = 5`: the maximum time (in seconds) to wait after loading the\n  package before forcibly shutting down the precompilation process (triggering\n  a test failure).\n- `expr::Expr = quote end`: An expression to run in the precompile package.\n\n!!! note\n\n    `Aqua.test_persistent_tasks(package)` creates a package with `package`\n    as a dependency and runs the precompilation process.\n    This requires that `package` is instantiable with the information in the\n    `Project.toml` file alone.\n    In particular, this will not work if some of `package`'s dependencies are `dev`ed\n    packages or are given as a local path or a git repository in the `Manifest.toml`.\n\"\"\"\nfunction test_persistent_tasks(package::PkgId; broken::Bool = false, kwargs...)\n    if broken\n        @test_broken !has_persistent_tasks(package; kwargs...)\n    else\n        @test !has_persistent_tasks(package; kwargs...)\n    end\nend\n\nfunction test_persistent_tasks(package::Module; kwargs...)\n    test_persistent_tasks(PkgId(package); kwargs...)\nend\n\nfunction has_persistent_tasks(package::PkgId; expr::Expr = quote end, tmax = 10)\n    root_project_path, found = root_project_toml(package)\n    found || error(\"Unable to locate Project.toml\")\n    return !precompile_wrapper(root_project_path, tmax, expr)\nend\n\n\"\"\"\n    Aqua.find_persistent_tasks_deps(package; kwargs...)\n\nTest all the dependencies of `package` with [`Aqua.test_persistent_tasks`](@ref).\n\nOn Julia 1.10 and higher, it returns a list of all dependencies failing the test.\nThese are likely the ones blocking precompilation of your package.\n\nAny `kwargs` are passed to [`Aqua.test_persistent_tasks`](@ref).\n\"\"\"\nfunction find_persistent_tasks_deps(package::PkgId; kwargs...)\n    root_project_path, found = root_project_toml(package)\n    found || error(\"Unable to locate Project.toml\")\n    prj = TOML.parsefile(root_project_path)\n    deps = get(prj, \"deps\", Dict{String,Any}())\n    filter!(deps) do (name, uuid)\n        id = PkgId(UUID(uuid), name)\n        return has_persistent_tasks(id; kwargs...)\n    end\n    return String[name for (name, _) in deps]\nend\n\nfunction find_persistent_tasks_deps(package::Module; kwargs...)\n    find_persistent_tasks_deps(PkgId(package); kwargs...)\nend\n\nfunction precompile_wrapper(project, tmax, expr)\n    @static if VERSION < v\"1.10.0-\"\n        return true\n    end\n    prev_project = Base.active_project()::String\n    isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(false)\n    try\n        pkgdir = dirname(project)\n        pkgname = get(TOML.parsefile(project), \"name\", \"\")::String\n        if isempty(pkgname)\n            @error \"Unable to locate package name in $project\"\n            return false\n        end\n        wrapperdir = tempname()\n        wrappername, _ = only(Pkg.generate(wrapperdir; io = devnull))\n        Pkg.activate(wrapperdir; io = devnull)\n        Pkg.develop(PackageSpec(path = pkgdir); io = devnull)\n        statusfile = joinpath(wrapperdir, \"done.log\")\n        open(joinpath(wrapperdir, \"src\", wrappername * \".jl\"), \"w\") do io\n            println(\n                io,\n                \"\"\"\nmodule $wrappername\nusing $pkgname\n$expr\n# Signal Aqua from the precompilation process that we've finished loading the package\nopen(\"$(escape_string(statusfile))\", \"w\") do io\n    println(io, \"done\")\n    flush(io)\nend\nend\n\"\"\",\n            )\n        end\n        # Precompile the wrapper package\n        currently_precompiling = @ccall(jl_generating_output()::Cint) == 1\n        cmd = if currently_precompiling\n            # During precompilation we run a dummy command that just touches the\n            # status file to keep things simple.\n            code = \"\"\"touch(\"$(escape_string(statusfile))\")\"\"\"\n            `$(Base.julia_cmd()) -e $code`\n        else\n            `$(Base.julia_cmd()) --project=$wrapperdir -e 'push!(LOAD_PATH, \"@stdlib\"); using Pkg; Pkg.precompile(; io = devnull)'`\n        end\n\n        cmd = pipeline(cmd; stdout, stderr)\n        proc = run(cmd; wait = false)::Base.Process\n        while !isfile(statusfile) && process_running(proc)\n            sleep(0.5)\n        end\n        if !isfile(statusfile)\n            @error \"Unexpected error: $statusfile was not created, but precompilation exited\"\n            return false\n        end\n        # Check whether precompilation finishes in the required time\n        t = time()\n        while process_running(proc) && time() - t < tmax\n            sleep(0.1)\n        end\n        success = !process_running(proc)\n        if !success\n            # SIGKILL to prevent julia from printing the SIG 15 handler, which can\n            # misleadingly look like it's caused by an issue in the user's program.\n            kill(proc, Base.SIGKILL)\n        end\n        return success\n    finally\n        isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(true)\n        Pkg.activate(prev_project; io = devnull)\n    end\nend\n"
  },
  {
    "path": "src/piracies.jl",
    "content": "module Piracy\n\nusing ..Aqua: is_kwcall\n\nusing Test: is_in_mods\n\n# based on Test/Test.jl#detect_ambiguities\n# https://github.com/JuliaLang/julia/blob/v1.9.1/stdlib/Test/src/Test.jl#L1838-L1896\nfunction all_methods(mods::Module...; skip_deprecated::Bool = true)\n    meths = Method[]\n    mods = collect(mods)::Vector{Module}\n\n    function examine_def(m::Method)\n        is_in_mods(m.module, true, mods) && push!(meths, m)\n        nothing\n    end\n\n    examine(mt::Core.MethodTable) = Base.visit(examine_def, mt)\n\n    if VERSION >= v\"1.12-\" && isdefined(Core, :methodtable)\n        examine(Core.methodtable)\n    elseif VERSION >= v\"1.12-\" && isdefined(Core, :GlobalMethods)\n        # for all versions between JuliaLang/julia#58131 and JuliaLang/julia#59158\n        # so just some 1.12 rcs and 1.13 DEVs\n        examine(Core.GlobalMethods)\n    else\n        work = Base.loaded_modules_array()\n        filter!(mod -> mod === parentmodule(mod), work) # some items in loaded_modules_array are not top modules (really just Base)\n        while !isempty(work)\n            mod = pop!(work)\n            for name in names(mod; all = true)\n                (skip_deprecated && Base.isdeprecated(mod, name)) && continue\n                isdefined(mod, name) || continue\n                f = Base.unwrap_unionall(getfield(mod, name))\n                if isa(f, Module) && f !== mod && parentmodule(f) === mod && nameof(f) === name\n                    push!(work, f)\n                elseif isa(f, DataType) &&\n                       isdefined(f.name, :mt) &&\n                       parentmodule(f) === mod &&\n                       nameof(f) === name &&\n                       f.name.mt !== Symbol.name.mt &&\n                       f.name.mt !== DataType.name.mt\n                    examine(f.name.mt)\n                end\n            end\n        end\n        examine(Symbol.name.mt)\n        examine(DataType.name.mt)\n    end\n    return meths\nend\n\n##################################\n# Generic fallback for type parameters that are instances, like the 1 in\n# Array{T, 1}\nis_foreign(@nospecialize(x), pkg::Base.PkgId; treat_as_own) =\n    is_foreign(typeof(x), pkg; treat_as_own = treat_as_own)\n\n# Symbols can be used as type params - we assume these are unique and not\n# piracy.  This implies that we have\n#\n#     julia> Aqua.Piracy.is_foreign(1, Base.PkgId(Aqua))\n#     true\n# \n#     julia> Aqua.Piracy.is_foreign(:hello, Base.PkgId(Aqua))\n#     false\n#\n# and thus\n#\n#     julia> Aqua.Piracy.is_foreign(Val{1}, Base.PkgId(Aqua))\n#     true\n# \n#     julia> Aqua.Piracy.is_foreign(Val{:hello}, Base.PkgId(Aqua))\n#     false\n#\n# Admittedly, this asymmetry is rather worrisome.  We do need to treat 1 foreign\n# to consider `Vector{Char}` (i.e., `Array{Char,1}`) foreign.  This may suggest\n# to treat the `Symbol` type foreign as well.  However, it means that we treat\n# definition such as\n#\n#     ForeignModule.api_function(::Val{:MyPackageName}) = ...\n# \n# as a type piracy even if this is actually the intended use-case (which is not\n# a crazy API).  The symbol name may also come from `gensym`.  Since the aim of\n# `Aqua.test_piracies` is to detect only \"obvious\" piracies, let us play on the\n# safe side.\nis_foreign(x::Symbol, pkg::Base.PkgId; treat_as_own) = false\n\nis_foreign_module(mod::Module, pkg::Base.PkgId) = Base.PkgId(mod) != pkg\n\nfunction is_foreign(@nospecialize(T::DataType), pkg::Base.PkgId; treat_as_own)\n    params = T.parameters\n    # For Type{Foo}, we consider it to originate from the same as Foo\n    C = getfield(parentmodule(T), nameof(T))\n    if C === Type\n        @assert length(params) == 1\n        return is_foreign(first(params), pkg; treat_as_own = treat_as_own)\n    else\n        # Both the type itself and all of its parameters must be foreign\n        return !((C in treat_as_own)::Bool) &&\n               is_foreign_module(parentmodule(T), pkg) &&\n               all(param -> is_foreign(param, pkg; treat_as_own = treat_as_own), params)\n    end\nend\n\nfunction is_foreign(@nospecialize(U::UnionAll), pkg::Base.PkgId; treat_as_own)\n    # We do not consider extending Set{T} to be piracies, if T is not foreign.\n    # Extending it goes against Julia style, but it's not piracies IIUC.\n    is_foreign(U.body, pkg; treat_as_own = treat_as_own) &&\n        is_foreign(U.var, pkg; treat_as_own = treat_as_own)\nend\n\nis_foreign(@nospecialize(T::TypeVar), pkg::Base.PkgId; treat_as_own) =\n    is_foreign(T.ub, pkg; treat_as_own = treat_as_own)\n\n# Before 1.7, Vararg was a UnionAll, so the UnionAll method will work\n@static if VERSION >= v\"1.7-\"\n    is_foreign(@nospecialize(T::Core.TypeofVararg), pkg::Base.PkgId; treat_as_own) =\n        is_foreign(T.T, pkg; treat_as_own = treat_as_own)\nend\n\nfunction is_foreign(@nospecialize(U::Union), pkg::Base.PkgId; treat_as_own)\n    # Even if Foo is local, overloading f(::Union{Foo, Int}) with foreign f is piracy.\n    any(T -> is_foreign(T, pkg; treat_as_own = treat_as_own), Base.uniontypes(U))\nend\n\nfunction is_foreign_method(@nospecialize(U::Union), pkg::Base.PkgId; treat_as_own)\n    # When installing a method for a union type, then we only consider it as\n    # foreign if *all* parameters of the union are foreign, i.e. overloading\n    # Union{Foo, Int}() is not piracy.\n    all(T -> is_foreign(T, pkg; treat_as_own = treat_as_own), Base.uniontypes(U))\nend\n\nfunction is_foreign_method(@nospecialize(x::Any), pkg::Base.PkgId; treat_as_own)\n    is_foreign(x, pkg; treat_as_own = treat_as_own)\nend\n\nfunction is_foreign_method(@nospecialize(T::DataType), pkg::Base.PkgId; treat_as_own)\n    params = T.parameters\n    # For Type{Foo}, we consider it to originate from the same as Foo\n    C = getfield(parentmodule(T), nameof(T))\n    if C === Type\n        @assert length(params) == 1\n        return is_foreign_method(first(params), pkg; treat_as_own = treat_as_own)\n    end\n\n    # fallback to general code\n    return !((T in treat_as_own)::Bool) &&\n           !(\n               T <: Function &&\n               isdefined(T, :instance) &&\n               (T.instance in treat_as_own)::Bool\n           ) &&\n           is_foreign(T, pkg; treat_as_own = treat_as_own)\nend\n\n\nfunction is_pirate(meth::Method; treat_as_own = Union{Function,Type}[])\n    method_pkg = Base.PkgId(meth.module)\n\n    signature = Base.unwrap_unionall(meth.sig)\n\n    function_type_index = 1\n    if is_kwcall(meth.sig)\n        # kwcall is a special case, since it is not a real function\n        # but a wrapper around a function, the third parameter is the original\n        # function, its positional arguments follow.\n        function_type_index += 2\n    end\n\n    # the first parameter in the signature is the function type, and it\n    # follows slightly other rules if it happens to be a Union type\n    is_foreign_method(\n        signature.parameters[function_type_index],\n        method_pkg;\n        treat_as_own = treat_as_own,\n    ) || return false\n\n    return all(\n        param -> is_foreign(param, method_pkg; treat_as_own = treat_as_own),\n        signature.parameters[function_type_index+1:end],\n    )\nend\n\nfunction hunt(mod::Module; skip_deprecated::Bool = true, kwargs...)\n    piracies = filter(all_methods(mod; skip_deprecated = skip_deprecated)) do method\n        method.module === mod && is_pirate(method; kwargs...)\n    end\n    sort!(piracies, by = (m -> m.name))\n    return piracies\nend\n\nend # module\n\n\"\"\"\n    test_piracies(m::Module)\n\nTest that `m` does not commit type piracies.\n\n# Keyword Arguments\n- `broken::Bool = false`: If true, it uses `@test_broken` instead of\n  `@test` and shortens the error message.\n- `skip_deprecated::Bool = true`: If true, it does not check deprecated methods.\n- `treat_as_own = Union{Function, Type}[]`: The types in this container\n  are considered to be \"owned\" by the module `m`. This is useful for\n  testing packages that deliberately commit some type piracies, e.g. modules\n  adding higher-level functionality to a lightweight C-wrapper, or packages\n  that are extending `StatsAPI.jl`.\n\"\"\"\nfunction test_piracies(m::Module; broken::Bool = false, kwargs...)\n    v = Piracy.hunt(m; kwargs...)\n    if broken\n        if !isempty(v)\n            printstyled(\n                stderr,\n                \"$(length(v)) instances of possible type-piracy detected. To get a list, set `broken = false`.\\n\";\n                bold = true,\n                color = Base.error_color(),\n            )\n        end\n        @test_broken isempty(v)\n    else\n        if !isempty(v)\n            printstyled(\n                stderr,\n                \"Possible type-piracy detected:\\n\";\n                bold = true,\n                color = Base.error_color(),\n            )\n            show(stderr, MIME\"text/plain\"(), v)\n            println(stderr)\n        end\n        @test isempty(v)\n    end\nend\n"
  },
  {
    "path": "src/precompile.jl",
    "content": "using PrecompileTools: @compile_workload\n\n# Create a minimal fake package to test. Needs to be at the top-level because\n# it's a module.\nmodule _FakePackage\nexport fake_function\nfake_function() = 1\nend\n\n@compile_workload begin\n    redirect_stdout(devnull) do\n        test_all(\n            _FakePackage;\n            ambiguities = false,\n            project_extras = false,\n            stale_deps = false,\n            deps_compat = false,\n            persistent_tasks = false,\n        )\n    end\n\n    # Explicitly precompile the tests that need a real package module\n    precompile(test_ambiguities, (Vector{Module},))\n    precompile(test_project_extras, (Module,))\n    precompile(test_stale_deps, (Module,))\n    precompile(test_deps_compat, (Module,))\n\n    # Create a fake package directory for testing persistent_tasks. We go to\n    # some effort to precompile this because it takes the longest due to Pkg\n    # calls and running precompilation in a subprocess.\n    mktempdir() do dir\n        project_file = joinpath(dir, \"Project.toml\")\n        write(\n            project_file,\n            \"\"\"\nname = \"AquaFakePackage\"\nuuid = \"5a23b2e7-8c45-4b1c-9d3f-7a6b4c8d9e0f\"\nversion = \"0.1.0\"\n\n[deps]\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[compat]\nTest = \"1\"\njulia = \"1\"\n\"\"\",\n        )\n\n        srcdir = joinpath(dir, \"src\")\n        mkdir(srcdir)\n        write(joinpath(srcdir, \"AquaFakePackage.jl\"), \"module AquaFakePackage end\")\n\n        # The meat of the compilation latency comes from this function. Running\n        # test_persistent_tasks() directly is more difficult because it takes a\n        # Module and then gets the package directory, and we don't want to load\n        # the fake package.\n        precompile_wrapper(project_file, 10, quote end)\n    end\nend\n"
  },
  {
    "path": "src/project_extras.jl",
    "content": "\"\"\"\n    test_project_extras(package::Union{Module, PkgId})\n    test_project_extras(packages::Vector{Union{Module, PkgId}})\n\nCheck that test target of the root project and test project\n(`test/Project.toml`) are consistent.\n\nThis is useful for supporting Julia < 1.2 while recording test-only dependency\ncompatibility in `test/Project.toml`.\n\"\"\"\nfunction test_project_extras(pkg::PkgId; kwargs...)\n    msgs = analyze_project_extras(pkg; kwargs...)\n    @test isempty(msgs)\nend\n\n# Remove with next breaking version\nfunction test_project_extras(packages::Vector{<:Union{Module,PkgId}}; kwargs...)\n    @testset \"$pkg\" for pkg in packages\n        test_project_extras(pkg; kwargs...)\n    end\nend\n\nfunction test_project_extras(mod::Module; kwargs...)\n    test_project_extras(aspkgid(mod); kwargs...)\nend\n\nis_julia12_or_later(compat::AbstractString) = is_julia12_or_later(semver_spec(compat))\nis_julia12_or_later(compat::VersionSpec) = isempty(compat ∩ semver_spec(\"1.0 - 1.1\"))\n\nfunction analyze_project_extras(pkg::PkgId)\n    root_project_path, found = root_project_toml(pkg)\n    found || error(\"Unable to locate Project.toml\")\n\n    test_project_path, found =\n        project_toml_path(joinpath(dirname(root_project_path), \"test\"))\n    found || return String[] # having no test/Project.toml is fine\n    root_project = TOML.parsefile(root_project_path)\n    test_project = TOML.parsefile(test_project_path)\n\n    # Ignore root project's extras if only supporting julia 1.2 or later.\n    # See: # https://julialang.github.io/Pkg.jl/v1/creating-packages/#Test-specific-dependencies-in-Julia-1.2-and-above-1\n    julia_version = get(get(root_project, \"compat\", Dict{String,Any}()), \"julia\", nothing)\n    isnothing(julia_version) && return String[\"Could not find `julia` compat.\"]\n    is_julia12_or_later(julia_version) && return String[]\n\n    # `extras_test_deps`: test-only dependencies according to Project.toml\n    deps =\n        PkgId[PkgId(UUID(v), k) for (k, v) in get(root_project, \"deps\", Dict{String,Any}())]\n    target =\n        Set{String}(get(get(root_project, \"targets\", Dict{String,Any}()), \"test\", String[]))\n    extras_test_deps = setdiff(\n        PkgId[\n            PkgId(UUID(v), k) for\n            (k, v) in get(root_project, \"extras\", Dict{String,Any}()) if k in target\n        ],\n        deps,\n    )\n\n    # `test_deps`: test-only dependencies according to test/Project.toml:\n    test_deps = setdiff(\n        PkgId[\n            PkgId(UUID(v), k) for (k, v) in get(test_project, \"deps\", Dict{String,Any}())\n        ],\n        deps,\n        [PkgId(UUID(root_project[\"uuid\"]), root_project[\"name\"])],\n    )\n\n    not_in_extras = setdiff(test_deps, extras_test_deps)\n    not_in_test = setdiff(extras_test_deps, test_deps)\n    if isempty(not_in_extras) && isempty(not_in_test)\n        return String[]\n    else\n        msgs = String[]\n        push!(\n            msgs,\n            \"Root and test projects should be consistent for projects supporting Julia <= 1.1.\",\n        )\n        if !isempty(not_in_extras)\n            msg = \"Test dependencies not in root project ($root_project_path):\"\n            for pkg in sort!(collect(not_in_extras); by = (pkg -> pkg.name))\n                msg *= \"\\n\\t$(pkg.name) [$(pkg.uuid)]\"\n            end\n            push!(msgs, msg)\n        end\n        if !isempty(not_in_test)\n            msg = \"Dependencies not in test project ($test_project_path):\"\n            for pkg in sort!(collect(not_in_test); by = (pkg -> pkg.name))\n                msg *= \"\\n\\t$(pkg.name) [$(pkg.uuid)]\"\n            end\n            push!(msgs, msg)\n        end\n\n        return msgs\n    end\nend\n"
  },
  {
    "path": "src/stale_deps.jl",
    "content": "\"\"\"\n    Aqua.test_stale_deps(package; ignore::AbstractVector{Symbol} = Symbol[])\n\nTest that `package` loads all dependencies listed in `Project.toml`.\n\nNote that this does not imply that `package` loads the dependencies\ndirectly, this can be achieved via transitivity as well.\n\n!!! note \"Weak dependencies and extensions\"\n\n    Due to the automatic loading of package extensions once all of\n    their trigger dependencies are loaded, Aqua.jl can, by design of julia,\n    not check if a package extension indeed loads all of its trigger\n    dependencies using `import` or `using`.\n\n!!! warning \"Known bug\"\n\n    Currently, `Aqua.test_stale_deps` does not detect stale\n    dependencies when they are in the sysimage. This is considered a\n    bug and may be fixed in the future. Such a release is considered\n    non-breaking.\n\n# Arguments\n- `packages`: a top-level `Module`, a `Base.PkgId`, or a collection of\n  them.\n\n# Keyword Arguments\n- `ignore`: names of dependent packages to be ignored.\n\"\"\"\nfunction test_stale_deps(pkg::PkgId; kwargs...)\n    stale_deps = find_stale_deps(pkg; kwargs...)\n    @test isempty(stale_deps)\nend\n\nfunction test_stale_deps(mod::Module; kwargs...)\n    test_stale_deps(aspkgid(mod); kwargs...)\nend\n\n# Remove in next breaking release\nfunction test_stale_deps(packages::Vector{<:Union{Module,PkgId}}; kwargs...)\n    @testset \"$pkg\" for pkg in packages\n        test_stale_deps(pkg; kwargs...)\n    end\nend\n\nfunction find_stale_deps(pkg::PkgId; ignore::AbstractVector{Symbol} = Symbol[])\n    root_project_path, found = root_project_toml(pkg)\n    found || error(\"Unable to locate Project.toml\")\n\n    prj = TOML.parsefile(root_project_path)\n    deps::Vector{PkgId} =\n        PkgId[PkgId(UUID(v), k) for (k::String, v::String) in get(prj, \"deps\", Dict())]\n    weakdeps::Vector{PkgId} =\n        PkgId[PkgId(UUID(v), k) for (k::String, v::String) in get(prj, \"weakdeps\", Dict())]\n\n    marker = \"_START_MARKER_\"\n    code = \"\"\"\n    $(Base.load_path_setup_code())\n    Base.require($(reprpkgid(pkg)))\n    print(\"$marker\")\n    for pkg in keys(Base.loaded_modules)\n        pkg.uuid === nothing || println(pkg.uuid)\n    end\n    \"\"\"\n    cmd = Base.julia_cmd()\n    output = read(`$cmd --startup-file=no --color=no -e $code`, String)\n    pos = findfirst(marker, output)\n    @assert !isnothing(pos)\n    output = output[pos.stop+1:end]\n    loaded_uuids = map(UUID, eachline(IOBuffer(output)))\n\n    return find_stale_deps_2(;\n        deps = deps,\n        weakdeps = weakdeps,\n        loaded_uuids = loaded_uuids,\n        ignore = ignore,\n    )\nend\n\n# Side-effect -free part of stale dependency analysis.\nfunction find_stale_deps_2(;\n    deps::AbstractVector{PkgId},\n    weakdeps::AbstractVector{PkgId},\n    loaded_uuids::AbstractVector{UUID},\n    ignore::AbstractVector{Symbol},\n)\n    deps_uuids = [p.uuid for p in deps]\n    pkgid_from_uuid = Dict(p.uuid => p for p in deps)\n\n    stale_uuids = setdiff(deps_uuids, loaded_uuids)\n    stale_pkgs = PkgId[pkgid_from_uuid[uuid] for uuid in stale_uuids]\n    stale_pkgs = setdiff(stale_pkgs, weakdeps)\n    stale_pkgs = PkgId[p for p in stale_pkgs if !(Symbol(p.name) in ignore)]\n\n    return stale_pkgs\nend\n"
  },
  {
    "path": "src/unbound_args.jl",
    "content": "\"\"\"\n    test_unbound_args(m::Module; broken::Bool = false)\n\nTest that all methods in `m` and its submodules do not have\nunbound type parameters.\n\nAn unbound type parameter is a type parameter with a `where`, that does not\noccur in the signature of some dispatch of the method.\n\n# Keyword Arguments\n- `broken`: If true, it uses `@test_broken` instead of\n  `@test` and shortens the error message.\n\"\"\"\nfunction test_unbound_args(m::Module; broken::Bool = false)\n    unbounds = detect_unbound_args_recursively(m)\n    if broken\n        if !isempty(unbounds)\n            printstyled(\n                stderr,\n                \"$(length(unbounds)) instances of unbound type parameters detected. To get a list, set `broken = false`.\\n\";\n                bold = true,\n                color = Base.error_color(),\n            )\n        end\n        @test_broken isempty(unbounds)\n    else\n        if !isempty(unbounds)\n            printstyled(\n                stderr,\n                \"Unbound type parameters detected:\\n\";\n                bold = true,\n                color = Base.error_color(),\n            )\n            show(stderr, MIME\"text/plain\"(), unbounds)\n            println(stderr)\n        end\n\n        @test isempty(unbounds)\n    end\nend\n\ndetect_unbound_args_recursively(m) = Test.detect_unbound_args(m; recursive = true)\n"
  },
  {
    "path": "src/undocumented_names.jl",
    "content": "\"\"\"\n    test_undocumented_names(m::Module; broken::Bool = false)\n\nTest that all public names in `m` and its recursive submodules have a docstring\n(not including `m` itself).\n\n!!! tip\n    On all Julia versions, public names include the exported names.\n    On Julia versions >= 1.11, public names also include the names annotated with the\n    `public` keyword.\n\n!!! warning\n    When running this Aqua test in Julia versions before 1.11, it does nothing.\n    Thus if you use continuous integration tests, make sure those are configured\n    to use Julia >= 1.11 in order to benefit from this test.\n\n# Keyword Arguments\n- `broken`: If true, it uses `@test_broken` instead of\n  `@test` and shortens the error message.\n\"\"\"\nfunction test_undocumented_names(m::Module; broken::Bool = false)\n    @static if VERSION >= v\"1.11\"\n        # exclude the module name itself because it has the README as auto-generated docstring (https://github.com/JuliaLang/julia/pull/39093)\n        undocumented_names = Symbol[]\n        walkmodules(m) do x\n            append!(undocumented_names, Docs.undocumented_names(x))\n        end\n        undocumented_names = filter(n -> n != nameof(m), undocumented_names)\n        if broken\n            @test_broken isempty(undocumented_names)\n        else\n            @test isempty(undocumented_names)\n        end\n    else\n        undocumented_names = Symbol[]\n    end\n    if !isempty(undocumented_names)\n        printstyled(\n            stderr,\n            \"Undocumented names detected:\\n\";\n            bold = true,\n            color = Base.error_color(),\n        )\n        !broken && show(stderr, MIME\"text/plain\"(), undocumented_names)\n        println(stderr)\n    end\nend\n"
  },
  {
    "path": "src/utils.jl",
    "content": "askwargs(kwargs) = (; kwargs...)\nfunction askwargs(flag::Bool)\n    if !flag\n        throw(ArgumentError(\"expect `true`\"))\n    end\n    return NamedTuple()\nend\n\naspkgids(pkg::Union{Module,PkgId}) = aspkgids([pkg])\naspkgids(packages) = mapfoldl(aspkgid, push!, packages, init = PkgId[])\n\naspkgid(pkg::PkgId) = pkg\nfunction aspkgid(m::Module)\n    if !ispackage(m)\n        error(\"Non-package (non-toplevel) module is not supported. Got: $m\")\n    end\n    return PkgId(m)\nend\nfunction aspkgid(name::Symbol)\n    # Maybe `Base.depwarn()`\n    return Base.identify_package(String(name))::PkgId\nend\n\nispackage(m::Module) =\n    if m in (Base, Core)\n        true\n    else\n        parentmodule(m) == m\n    end\n\nfunction project_toml_path(dir)\n    candidates = joinpath.(dir, [\"Project.toml\", \"JuliaProject.toml\"])\n    i = findfirst(isfile, candidates)\n    i === nothing && return candidates[1], false\n    return candidates[i], true\nend\n\nfunction root_project_toml(pkg::PkgId)\n    srcpath = Base.locate_package(pkg)\n    srcpath === nothing && return \"\", false\n    pkgpath = dirname(dirname(srcpath))\n    root_project_path, found = project_toml_path(pkgpath)\n    return root_project_path, found\nend\n\nmodule _TempModule end\n\neval_string(code::AbstractString) = include_string(_TempModule, code)\n\nfunction checked_repr(obj)\n    code = repr(obj)\n    if !isequal(eval_string(code), obj)\n        error(\"`$repr` is not `repr`-safe\")\n    end\n    return code\nend\n\nfunction is_kwcall(signature::Type)\n    @static if VERSION < v\"1.9\"\n        signature = Base.unwrap_unionall(signature)::DataType\n        try\n            length(signature.parameters) >= 3 || return false\n            signature <: Tuple{Function,Any,Any,Vararg} || return false\n            (signature.parameters[3] isa DataType && signature.parameters[3] <: Type) ||\n                isconcretetype(signature.parameters[3]) ||\n                return false\n            return signature.parameters[1] === Core.kwftype(signature.parameters[3])\n        catch err\n            @warn \"Please open an issue on JuliaTesting/Aqua.jl for \\\"is_kwcall\\\" and the following data:\" signature err\n            return false\n        end\n    else\n        return signature <: Tuple{typeof(Core.kwcall), Any, Any, Vararg}\n    end\nend\n"
  },
  {
    "path": "test/pkgs/AquaTesting.jl",
    "content": "module AquaTesting\n\nusing Base: PkgId, UUID\nusing Pkg\nusing Test\n\n# Taken from Test/test/runtests.jl\nmutable struct NoThrowTestSet <: Test.AbstractTestSet\n    results::Vector\n    NoThrowTestSet(desc) = new([])\nend\nTest.record(ts::NoThrowTestSet, t::Test.Result) = (push!(ts.results, t); t)\nTest.finish(ts::NoThrowTestSet) = ts.results\n\n\nmacro testtestset(args...)\n    @gensym TestSet\n    expr = quote\n        $TestSet = $NoThrowTestSet\n        $Test.@testset($TestSet, $(args...))\n    end\n    esc(expr)\nend\n\n\nconst SAMPLE_PKGIDS = [\n    PkgId(UUID(\"1649c42c-2196-4c52-9963-79822cd6227b\"), \"PkgWithIncompatibleTestProject\"),\n    PkgId(UUID(\"6e4a843a-fdff-4fa3-bb5a-e4ae67826963\"), \"PkgWithCompatibleTestProject\"),\n    PkgId(UUID(\"7231ce0e-e308-4079-b49f-19e33cc3ac6e\"), \"PkgWithPostJulia12Support\"),\n    PkgId(UUID(\"8981f3dd-97fd-4684-8ec7-7b0c42f64e2e\"), \"PkgWithoutTestProject\"),\n    PkgId(UUID(\"3922d3f4-c8f6-c8a8-00da-60b44ed8eac6\"), \"PkgWithoutDeps\"),\n]\n\nconst SAMPLE_PKG_BY_NAME = Dict(pkg.name => pkg for pkg in SAMPLE_PKGIDS)\n\nfunction with_sample_pkgs(f)\n    sampledir = joinpath(@__DIR__, \"sample\")\n\n    original_load_path = copy(LOAD_PATH)\n    try\n        pushfirst!(LOAD_PATH, sampledir)\n        f()\n    finally\n        append!(empty!(LOAD_PATH), original_load_path)\n    end\nend\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/PersistentTask/Project.toml",
    "content": "name = \"PersistentTask\"\nuuid = \"e5c298b6-d81d-47aa-a9ed-5ea983e22986\"\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/PersistentTask/src/PersistentTask.jl",
    "content": "module PersistentTask\n\nconst t = Ref{Any}()\n__init__() = t[] = Timer(0.1; interval = 1)   # create a persistent `Timer` `Task`\n\nend # module PersistentTask\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/TransientTask/Project.toml",
    "content": "name = \"TransientTask\"\nuuid = \"94ae9332-58b0-4342-989c-0a7e44abcca9\"\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/TransientTask/src/TransientTask.jl",
    "content": "module TransientTask\n\n__init__() = Timer(0.1)   # create a transient `Timer` `Task`\n\nend # module TransientTask\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/UsesBoth/Project.toml",
    "content": "name = \"UsesBoth\"\nuuid = \"96f12b6e-60f8-43dc-b131-049a88a2f499\"\n\n[deps]\nPersistentTask = \"e5c298b6-d81d-47aa-a9ed-5ea983e22986\"\nTransientTask = \"94ae9332-58b0-4342-989c-0a7e44abcca9\"\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/UsesBoth/src/UsesBoth.jl",
    "content": "module UsesBoth\n\nusing TransientTask\nusing PersistentTask\n\nend # module UsesBoth\n"
  },
  {
    "path": "test/pkgs/PiracyForeignProject/Project.toml",
    "content": "name = \"PiracyForeignProject\"\nuuid = \"f592ac8b-a2e8-4dd0-be7a-e4053dab5b76\"\n"
  },
  {
    "path": "test/pkgs/PiracyForeignProject/src/PiracyForeignProject.jl",
    "content": "module PiracyForeignProject\n\nstruct ForeignType end\nstruct ForeignParameterizedType{T} end\n\nstruct ForeignNonSingletonType\n    x::Int\nend\n\nend\n"
  },
  {
    "path": "test/pkgs/PkgUnboundArgs.jl",
    "content": "module PkgUnboundArgs\n\n# Putting it in a submodule to test that `recursive=true` is used.\nmodule M25341\n_totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = E\nend\n# `_totuple` is taken from\n# https://github.com/JuliaLang/julia/blob/48634f9f8669e1dc1be0a1589cd5be880c04055a/test/ambiguous.jl#L257-L259\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PkgWithAmbiguities.jl",
    "content": "module PkgWithAmbiguities\n\n# 1 ambiguity\nf(::Any, ::Int) = 1\nf(::Int, ::Any) = 2\nconst num_ambs_f = 1\n\n# 2 ambiguities:\n#   1 for g\n#   1 for Core.kwfunc(g)\ng(::Any, ::Int; kw) = 1\ng(::Int, ::Any; kw) = 2\nconst num_ambs_g = 2\n\nabstract type AbstractType end\nstruct SingletonType <: AbstractType end\n\nstruct ConcreteType <: AbstractType\n    x::Int\nend\n\n# 2 ambiguities\nSingletonType(::Any, ::Any, ::Int) = 1\nSingletonType(::Any, ::Int, ::Int) = 2\nSingletonType(::Int, ::Any, ::Any) = 3\n\n# 1 ambiguity\n(::SingletonType)(::Any, ::Float64) = 1\n(::SingletonType)(::Float64, ::Any) = 2\n\nconst num_ambs_SingletonType = 3\n\n# 3 ambiguities\nConcreteType(::Any, ::Any, ::Int) = 1\nConcreteType(::Any, ::Int, ::Any) = 2\nConcreteType(::Int, ::Any, ::Any) = 3\n\n# 1 ambiguity\n(::ConcreteType)(::Any, ::Float64) = 1\n(::ConcreteType)(::Float64, ::Any) = 2\n\nconst num_ambs_ConcreteType = 4\n\n# 1 ambiguity\nabstract type AbstractParameterizedType{T} end\nstruct ConcreteParameterizedType{T} <: AbstractParameterizedType{T} end\n(::AbstractParameterizedType{T})(::Tuple{Tuple{Int}}) where {T} = 1\n(::ConcreteParameterizedType)(::Tuple) = 2\n\nconst num_ambs_ParameterizedType = 1\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PkgWithUndefinedExports.jl",
    "content": "module PkgWithUndefinedExports\n\nexport undefined_name\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PkgWithUndocumentedNames.jl",
    "content": "module PkgWithUndocumentedNames\n\n\"\"\"\n    documented_function\n\"\"\"\nfunction documented_function end\n\nfunction undocumented_function end\n\n\"\"\"\n    DocumentedStruct\n\"\"\"\nstruct DocumentedStruct end\n\nstruct UndocumentedStruct end\n\nexport documented_function, DocumentedStruct\nexport undocumented_function, UndocumentedStruct\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PkgWithUndocumentedNamesInSubmodule.jl",
    "content": "module PkgWithUndocumentedNamesInSubmodule\n\n\"\"\"\n    DocumentedStruct\n\"\"\"\nstruct DocumentedStruct end\n\nmodule SubModule\n\nstruct UndocumentedStruct end\n\nend\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PkgWithoutUndocumentedNames.jl",
    "content": "\"\"\"\n    PkgWithoutUndocumentedNames\n\"\"\"\nmodule PkgWithoutUndocumentedNames\n\n\"\"\"\n    documented_function\n\"\"\"\nfunction documented_function end\n\n\"\"\"\n    DocumentedStruct\n\"\"\"\nstruct DocumentedStruct end\n\nexport documented_function, DocumentedStruct\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithCompatibleTestProject/Project.toml",
    "content": "name = \"PkgWithCompatibleTestProject\"\nuuid = \"6e4a843a-fdff-4fa3-bb5a-e4ae67826963\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Pkg\", \"Test\"]\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithCompatibleTestProject/src/PkgWithCompatibleTestProject.jl",
    "content": "module PkgWithCompatibleTestProject end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithCompatibleTestProject/test/Project.toml",
    "content": "[deps]\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithIncompatibleTestProject/Project.toml",
    "content": "name = \"PkgWithIncompatibleTestProject\"\nuuid = \"1649c42c-2196-4c52-9963-79822cd6227b\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nREPL = \"3fa0cd96-eef1-5676-8a61-b3b8758bbffb\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"REPL\", \"Test\"]\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithIncompatibleTestProject/src/PkgWithIncompatibleTestProject.jl",
    "content": "module PkgWithIncompatibleTestProject end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithIncompatibleTestProject/test/Project.toml",
    "content": "[deps]\nRandom = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithPostJulia12Support/Project.toml",
    "content": "name = \"PkgWithPostJulia12Support\"\nuuid = \"7231ce0e-e308-4079-b49f-19e33cc3ac6e\"\n\n[compat]\njulia = \"1.2\"\n\n[extras]\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Pkg\", \"Test\"]\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithPostJulia12Support/src/PkgWithPostJulia12Support.jl",
    "content": "module PkgWithPostJulia12Support end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithPostJulia12Support/test/Project.toml",
    "content": "[deps]\nREPL = \"3fa0cd96-eef1-5676-8a61-b3b8758bbffb\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutDeps/Project.toml",
    "content": "name = \"PkgWithoutDeps\"\nuuid = \"3922d3f4-c8f6-c8a8-00da-60b44ed8eac6\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Test\"]\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutDeps/src/PkgWithoutDeps.jl",
    "content": "module PkgWithoutDeps end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutDeps/test/.gitkeep",
    "content": ""
  },
  {
    "path": "test/pkgs/sample/PkgWithoutTestProject/Project.toml",
    "content": "name = \"PkgWithoutTestProject\"\nuuid = \"8981f3dd-97fd-4684-8ec7-7b0c42f64e2e\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Test\"]\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutTestProject/src/PkgWithoutTestProject.jl",
    "content": "module PkgWithoutTestProject end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutTestProject/test/.gitkeep",
    "content": ""
  },
  {
    "path": "test/preamble.jl",
    "content": "let path = joinpath(@__DIR__, \"pkgs\")\n    if path ∉ LOAD_PATH\n        pushfirst!(LOAD_PATH, path)\n    end\nend\n\nusing Test\nusing Aqua\nusing AquaTesting: @testtestset, AquaTesting, with_sample_pkgs\n"
  },
  {
    "path": "test/runtests.jl",
    "content": "module TestAqua\n\nusing Test\n\n@testset \"$file\" for file in sort([\n    file for file in readdir(@__DIR__) if match(r\"^test_.*\\.jl$\", file) !== nothing\n])\n    include(file)\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_ambiguities.jl",
    "content": "module TestAmbiguities\n\ninclude(\"preamble.jl\")\n\n@testset begin\n    using PkgWithAmbiguities\n\n    using PkgWithAmbiguities:\n        num_ambs_f,\n        num_ambs_g,\n        num_ambs_SingletonType,\n        num_ambs_ConcreteType,\n        num_ambs_ParameterizedType\n    total =\n        num_ambs_f +\n        num_ambs_g +\n        num_ambs_SingletonType +\n        num_ambs_ConcreteType +\n        num_ambs_ParameterizedType\n\n    function check_testcase(exclude, num_ambiguities::Int; broken::Bool = false)\n        pkgids = Aqua.aspkgids([PkgWithAmbiguities, Core]) # include Core to find constructor ambiguities\n        num_ambiguities_, strout, strerr = Aqua._find_ambiguities(pkgids; exclude = exclude)\n        if broken\n            @test_broken num_ambiguities_ == num_ambiguities\n        else\n            if num_ambiguities_ != num_ambiguities\n                @show exclude\n                println(strout)\n                println(strerr)\n            end\n            @test num_ambiguities_ == num_ambiguities\n        end\n    end\n\n    check_testcase([], total)\n\n    # exclude just anything irrelevant, see #49\n    check_testcase([convert], total)\n\n    # exclude function\n    check_testcase([PkgWithAmbiguities.f], total - num_ambs_f)\n\n    # exclude function and kwsorter\n    check_testcase([PkgWithAmbiguities.g], total - num_ambs_g)\n\n    # exclude callables and constructors\n    check_testcase([PkgWithAmbiguities.SingletonType], total - num_ambs_SingletonType)\n    check_testcase([PkgWithAmbiguities.ConcreteType], total - num_ambs_ConcreteType)\n\n    # exclude abstract supertype without callables and constructors\n    check_testcase([PkgWithAmbiguities.AbstractType], total)\n\n    # for ambiguities between abstract and concrete type callables, only one needs to be excluded\n    check_testcase(\n        [PkgWithAmbiguities.AbstractParameterizedType],\n        total - num_ambs_ParameterizedType,\n    )\n    check_testcase(\n        [PkgWithAmbiguities.ConcreteParameterizedType],\n        total - num_ambs_ParameterizedType,\n    )\n\n    # exclude everything\n    check_testcase(\n        [\n            PkgWithAmbiguities.f,\n            PkgWithAmbiguities.g,\n            PkgWithAmbiguities.SingletonType,\n            PkgWithAmbiguities.ConcreteType,\n            PkgWithAmbiguities.ConcreteParameterizedType,\n        ],\n        0,\n    )\n\n\n    # It works with other tests:\n    Aqua.test_unbound_args(PkgWithAmbiguities)\n    Aqua.test_undefined_exports(PkgWithAmbiguities)\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_deps_compat.jl",
    "content": "module TestDepsCompat\n\ninclude(\"preamble.jl\")\nusing Aqua: find_missing_deps_compat, has_julia_compat\n\nconst DictSA = Dict{String,Any}\n\n@testset \"has_julia_compat\" begin\n    @test has_julia_compat(DictSA(\"compat\" => DictSA(\"julia\" => \"1\")))\n    @test has_julia_compat(DictSA(\"compat\" => DictSA(\"julia\" => \"1.0\")))\n    @test has_julia_compat(DictSA(\"compat\" => DictSA(\"julia\" => \"1.6\")))\n    @test has_julia_compat(\n        DictSA(\n            \"deps\" => DictSA(\"PkgA\" => \"229717a1-0d13-4dfb-ba8f-049672e31205\"),\n            \"compat\" => DictSA(\"julia\" => \"1\", \"PkgA\" => \"1.0\"),\n        ),\n    )\n\n    @test !has_julia_compat(DictSA())\n    @test !has_julia_compat(DictSA(\"compat\" => DictSA()))\n    @test !has_julia_compat(DictSA(\"compat\" => DictSA(\"PkgA\" => \"1.0\")))\n    @test !has_julia_compat(\n        DictSA(\n            \"deps\" => DictSA(\"PkgA\" => \"229717a1-0d13-4dfb-ba8f-049672e31205\"),\n            \"compat\" => DictSA(\"PkgA\" => \"1.0\"),\n        ),\n    )\nend\n\n@testset \"find_missing_deps_compat\" begin\n    @testset \"pass\" begin\n        result = find_missing_deps_compat(\n            DictSA(\"deps\" => DictSA(), \"compat\" => DictSA(\"julia\" => \"1\")),\n            \"deps\",\n        )\n        @test isempty(result)\n\n        result = find_missing_deps_compat(\n            DictSA(\n                \"deps\" => DictSA(\"PkgA\" => \"229717a1-0d13-4dfb-ba8f-049672e31205\"),\n                \"compat\" => DictSA(\"julia\" => \"1\", \"PkgA\" => \"1.0\"),\n            ),\n            \"deps\",\n        )\n        @test isempty(result)\n        @testset \"does not have `deps`\" begin\n            result = find_missing_deps_compat(DictSA(), \"deps\")\n            @test isempty(result)\n        end\n    end\n    @testset \"failure\" begin\n        @testset \"does not have `compat`\" begin\n            result = find_missing_deps_compat(\n                DictSA(\"deps\" => DictSA(\"PkgA\" => \"229717a1-0d13-4dfb-ba8f-049672e31205\")),\n                \"deps\",\n            )\n            @test length(result) == 1\n            @test [pkg.name for pkg in result] == [\"PkgA\"]\n        end\n\n        @testset \"does not specify `compat` for PkgA\" begin\n            result = find_missing_deps_compat(\n                DictSA(\n                    \"deps\" => DictSA(\"PkgA\" => \"229717a1-0d13-4dfb-ba8f-049672e31205\"),\n                    \"compat\" => DictSA(\"julia\" => \"1\"),\n                ),\n                \"deps\",\n            )\n            @test length(result) == 1\n            @test [pkg.name for pkg in result] == [\"PkgA\"]\n        end\n\n        @testset \"does not specify `compat` for PkgB\" begin\n            result = find_missing_deps_compat(\n                DictSA(\n                    \"deps\" => DictSA(\n                        \"PkgA\" => \"229717a1-0d13-4dfb-ba8f-049672e31205\",\n                        \"PkgB\" => \"3d97d89c-7c41-49ae-981c-14fe13cc7943\",\n                    ),\n                    \"compat\" => DictSA(\"julia\" => \"1\", \"PkgA\" => \"1.0\"),\n                ),\n                \"deps\",\n            )\n            @test length(result) == 1\n            @test [pkg.name for pkg in result] == [\"PkgB\"]\n        end\n\n        @testset \"does not specify `compat` for stdlib\" begin\n            result = find_missing_deps_compat(\n                DictSA(\n                    \"deps\" => DictSA(\n                        \"LinearAlgebra\" => \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\",\n                    ),\n                    \"compat\" => DictSA(\"julia\" => \"1\"),\n                ),\n                \"deps\",\n            )\n            @test length(result) == 1\n            @test [pkg.name for pkg in result] == [\"LinearAlgebra\"]\n        end\n    end\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_exclude.jl",
    "content": "module TestExclude\n\ninclude(\"preamble.jl\")\nusing Base: PkgId\nusing Aqua: getexclude, normalize_exclude, normalize_exclude_obj, normalize_and_check_exclude, rootmodule, reprexclude\n\n@assert parentmodule(Tuple) === Core\n@assert parentmodule(foldl) === Base\n@assert parentmodule(Some) === Base\n@assert parentmodule(Broadcast.Broadcasted) === Base.Broadcast\n@assert rootmodule(Broadcast.Broadcasted) === Base\n\n@testset \"roundtrip\" begin\n    @testset for x in Any[\n        foldl\n        Some\n        Tuple\n        Broadcast.Broadcasted\n        nothing\n        Any\n    ]\n        @test getexclude(normalize_exclude(x)) === normalize_exclude_obj(x)\n    end\n    @test_throws ErrorException normalize_and_check_exclude(Any[Pair{Int}])\n\n    @testset \"$(repr(last(spec)))\" for spec in [\n        (PkgId(Base) => \"Base.#foldl\")\n        (PkgId(Base) => \"Base.Some\")\n        (PkgId(Core) => \"Core.Tuple\")\n        (PkgId(Base) => \"Base.Broadcast.Broadcasted\")\n        (PkgId(Core) => \"Core.Nothing\")\n        (PkgId(Core) => \"Core.Any\")\n    ]\n        @test normalize_exclude(getexclude(spec)) === spec\n    end\nend\n\n@testset \"normalize_and_check_exclude\" begin\n    @testset \"$i\" for (i, exclude) in enumerate([Any[foldl], Any[foldl, Some], Any[foldl, Tuple]])\n        local specs\n        @test (specs = normalize_and_check_exclude(exclude)) isa Vector\n        @test Base.include_string(@__MODULE__, reprexclude(specs)) == specs\n    end\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_persistent_tasks.jl",
    "content": "module TestPersistentTasks\n\ninclude(\"preamble.jl\")\nusing Base: PkgId, UUID\nusing Pkg: TOML\n\nfunction getid(name)\n    path = joinpath(@__DIR__, \"pkgs\", \"PersistentTasks\", name)\n    if path ∉ LOAD_PATH\n        pushfirst!(LOAD_PATH, path)\n    end\n    prj = TOML.parsefile(joinpath(path, \"Project.toml\"))\n    return PkgId(UUID(prj[\"uuid\"]), prj[\"name\"])\nend\n\n\n@testset \"PersistentTasks\" begin\n    @test !Aqua.has_persistent_tasks(getid(\"TransientTask\"))\n\n    result = Aqua.find_persistent_tasks_deps(getid(\"TransientTask\"))\n    @test result == []\n\n    if Base.VERSION >= v\"1.10-\"\n        @test Aqua.has_persistent_tasks(getid(\"PersistentTask\"))\n\n        result = Aqua.find_persistent_tasks_deps(getid(\"UsesBoth\"))\n        @test result == [\"PersistentTask\"]\n    end\n    filter!(str -> !occursin(\"PersistentTasks\", str), LOAD_PATH)\nend\n\n@testset \"test_persistent_tasks(expr)\" begin\n    if Base.VERSION >= v\"1.10-\"\n        @test !Aqua.has_persistent_tasks(\n            getid(\"TransientTask\"),\n            expr = quote\n                fetch(Threads.@spawn nothing)\n            end,\n        )\n        @test Aqua.has_persistent_tasks(getid(\"TransientTask\"), expr = quote\n            Threads.@spawn while true\n                sleep(0.5)\n            end\n        end)\n    end\nend\n\nend\n"
  },
  {
    "path": "test/test_piracy.jl",
    "content": "push!(LOAD_PATH, joinpath(@__DIR__, \"pkgs\", \"PiracyForeignProject\"))\n\nbaremodule PiracyModule\n\nusing PiracyForeignProject: ForeignType, ForeignParameterizedType, ForeignNonSingletonType\n\nusing Base:\n    Base,\n    Set,\n    AbstractSet,\n    Integer,\n    Val,\n    Vararg,\n    Vector,\n    Unsigned,\n    UInt,\n    String,\n    Tuple,\n    AbstractChar\n\nstruct Foo end\nstruct Bar{T<:AbstractSet{<:Integer}} end\n\n# Not piracy: Function defined here\nf(::Int, ::Union{String,Char}) = 1\nf(::Int) = 2\nFoo(::Int) = Foo()\n\n# Not piracy: At least one argument is local\nBase.findlast(::Foo, x::Int) = x + 1\nBase.findlast(::Set{Foo}, x::Int) = x + 1\nBase.findlast(::Type{Val{Foo}}, x::Int) = x + 1\nBase.findlast(::Tuple{Vararg{Bar{Set{Int}}}}, x::Int) = x + 1\nBase.findlast(::Val{:foo}, x::Int) = x + 1\nBase.findlast(::ForeignParameterizedType{Foo}, x::Int) = x + 1\n\n# Not piracy\nconst MyUnion = Union{Int,Foo}\nMyUnion(x::Int) = x\nMyUnion(; x::Int) = x\n\nexport MyUnion\n\n# Piracy\nBase.findfirst(::Set{Vector{Char}}, ::Int) = 1\nBase.findfirst(::Union{Foo,Bar{Set{Unsigned}},UInt}, ::Tuple{Vararg{String}}) = 1\nBase.findfirst(::AbstractChar, ::Set{T}) where {Int <: T <: Integer} = 1\n(::ForeignType)(x::Int8) = x + 1\n(::ForeignNonSingletonType)(x::Int8) = x + 1\n\n# Piracy, but not for `ForeignType in treat_as_own`\nBase.findmax(::ForeignType, x::Int) = x + 1\nBase.findmax(::Set{Vector{ForeignType}}, x::Int) = x + 1\nBase.findmax(::Union{Foo,ForeignType}, x::Int) = x + 1\n\n# Piracy, but not for `ForeignParameterizedType in treat_as_own`\nBase.findmin(::ForeignParameterizedType{Int}, x::Int) = x + 1\nBase.findmin(::Set{Vector{ForeignParameterizedType{Int}}}, x::Int) = x + 1\nBase.findmin(::Union{Foo,ForeignParameterizedType{Int}}, x::Int) = x + 1\n\nend # PiracyModule\n\nusing Aqua: Piracy\nusing PiracyForeignProject: ForeignType, ForeignParameterizedType, ForeignNonSingletonType\n\n# Get all methods - test length\nmeths = filter(Piracy.all_methods(PiracyModule)) do m\n    m.module == PiracyModule\nend\n\n@test length(meths) ==\n      2 + # Foo constructors\n      1 + # Bar constructor\n      2 + # f\n      4 + # MyUnion (incl. kwcall)\n      6 + # findlast\n      3 + # findfirst\n      1 + # ForeignType callable\n      1 + # ForeignNonSingletonType callable\n      3 + # findmax\n      3   # findmin\n\n# Test what is foreign\nBasePkg = Base.PkgId(Base)\nCorePkg = Base.PkgId(Core)\nThisPkg = Base.PkgId(PiracyModule)\n\n@test Piracy.is_foreign(Int, BasePkg; treat_as_own = []) # from Core\n@test !Piracy.is_foreign(Int, CorePkg; treat_as_own = []) # from Core\n@test !Piracy.is_foreign(Set{Int}, BasePkg; treat_as_own = [])\n@test !Piracy.is_foreign(Set{Int}, CorePkg; treat_as_own = [])\n\n# Test what is pirate\npirates = Piracy.hunt(PiracyModule)\n@test length(pirates) ==\n      3 + # findfirst\n      3 + # findmax\n      3 + # findmin\n      1 + # ForeignType callable\n      1   # ForeignNonSingletonType callable\n@test all(pirates) do m\n    m.name in [:findfirst, :findmax, :findmin, :ForeignType, :ForeignNonSingletonType]\nend\n\n# Test what is pirate (with treat_as_own=[ForeignType])\npirates = Piracy.hunt(PiracyModule, treat_as_own = [ForeignType])\n@test length(pirates) ==\n      3 + # findfirst\n      3 + # findmin\n      1   # ForeignNonSingletonType callable\n@test all(pirates) do m\n    m.name in [:findfirst, :findmin, :ForeignNonSingletonType]\nend\n\n# Test what is pirate (with treat_as_own=[ForeignParameterizedType])\npirates = Piracy.hunt(PiracyModule, treat_as_own = [ForeignParameterizedType])\n@test length(pirates) ==\n      3 + # findfirst\n      3 + # findmax\n      1 + # ForeignType callable\n      1   # ForeignNonSingletonType callable\n@test all(pirates) do m\n    m.name in [:findfirst, :findmax, :ForeignType, :ForeignNonSingletonType]\nend\n\n# Test what is pirate (with treat_as_own=[ForeignType, ForeignParameterizedType])\npirates = filter(\n    m -> Piracy.is_pirate(m; treat_as_own = [ForeignType, ForeignParameterizedType]),\n    meths,\n)\n@test length(pirates) ==\n      3 + # findfirst\n      1   # ForeignNonSingletonType callable\n@test all(pirates) do m\n    m.name in [:findfirst, :ForeignNonSingletonType]\nend\n\n# Test what is pirate (with treat_as_own=[Base.findfirst, Base.findmax])\npirates = Piracy.hunt(PiracyModule, treat_as_own = [Base.findfirst, Base.findmax])\n@test length(pirates) ==\n      3 + # findmin\n      1 + # ForeignType callable\n      1   # ForeignNonSingletonType callable\n@test all(pirates) do m\n    m.name in [:findmin, :ForeignType, :ForeignNonSingletonType]\nend\n\n# Test what is pirate (excluding a cover of everything)\npirates = filter(\n    m -> Piracy.is_pirate(\n        m;\n        treat_as_own = [\n            ForeignType,\n            ForeignParameterizedType,\n            ForeignNonSingletonType,\n            Base.findfirst,\n        ],\n    ),\n    meths,\n)\n@test length(pirates) == 0\n"
  },
  {
    "path": "test/test_project_extras.jl",
    "content": "module TestProjectExtras\n\ninclude(\"preamble.jl\")\nusing Aqua: is_julia12_or_later\nusing Base: PkgId, UUID\n\n@testset \"is_julia12_or_later\" begin\n    @test is_julia12_or_later(\"1.2\")\n    @test is_julia12_or_later(\"1.3\")\n    @test is_julia12_or_later(\"1.3, 1.4\")\n    @test is_julia12_or_later(\"1.3 - 1.4, 1.6\")\n    @test !is_julia12_or_later(\"1\")\n    @test !is_julia12_or_later(\"1.1\")\n    @test !is_julia12_or_later(\"1.0 - 1.1\")\n    @test !is_julia12_or_later(\"1.0 - 1.3\")\nend\n\nwith_sample_pkgs() do\n    @testset \"PkgWithIncompatibleTestProject\" begin\n        pkg = AquaTesting.SAMPLE_PKG_BY_NAME[\"PkgWithIncompatibleTestProject\"]\n        result = Aqua.analyze_project_extras(pkg)\n        @test !isempty(result)\n        @test any(\n            msg -> occursin(\n                \"Root and test projects should be consistent for projects supporting Julia <= 1.1.\",\n                msg,\n            ),\n            result,\n        )\n        @test any(\n            msg ->\n                occursin(\"Test dependencies not in root project\", msg) &&\n                    occursin(\"Random [9a3f8284-a2c9-5f02-9a11-845980a1fd5c]\", msg),\n            result,\n        )\n        @test any(\n            msg ->\n                occursin(\"Dependencies not in test project\", msg) &&\n                    occursin(\"REPL [3fa0cd96-eef1-5676-8a61-b3b8758bbffb]\", msg),\n            result,\n        )\n        @test !any(msg -> occursin(\"Test [\", msg), result)\n    end\n\n    @testset \"PkgWithCompatibleTestProject\" begin\n        pkg = AquaTesting.SAMPLE_PKG_BY_NAME[\"PkgWithCompatibleTestProject\"]\n        result = Aqua.analyze_project_extras(pkg)\n        @test isempty(result)\n    end\n\n    @testset \"PkgWithPostJulia12Support\" begin\n        pkg = AquaTesting.SAMPLE_PKG_BY_NAME[\"PkgWithPostJulia12Support\"]\n        result = Aqua.analyze_project_extras(pkg)\n        @test isempty(result)\n    end\n\n    @testset \"PkgWithoutTestProject\" begin\n        pkg = AquaTesting.SAMPLE_PKG_BY_NAME[\"PkgWithoutTestProject\"]\n        result = Aqua.analyze_project_extras(pkg)\n        @test isempty(result)\n    end\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_smoke.jl",
    "content": "module TestSmoke\n\nusing Aqua\n\n# test defaults\nAqua.test_all(Aqua)\n\n# test everything else\nAqua.test_all(\n    Aqua;\n    ambiguities = false,\n    unbound_args = false,\n    undefined_exports = false,\n    project_extras = false,\n    stale_deps = false,\n    deps_compat = false,\n    piracies = false,\n    persistent_tasks = false,\n)\n\nend  # module\n"
  },
  {
    "path": "test/test_stale_deps.jl",
    "content": "module TestStaleDeps\n\ninclude(\"preamble.jl\")\nusing Base: PkgId, UUID\nusing Aqua: find_stale_deps_2\n\n@testset \"find_stale_deps_2\" begin\n    pkg = PkgId(UUID(42), \"TargetPkg\")\n\n    dep1 = PkgId(UUID(1), \"Dep1\")\n    dep2 = PkgId(UUID(2), \"Dep2\")\n    dep3 = PkgId(UUID(3), \"Dep3\")\n\n    @testset \"pass\" begin\n        result = find_stale_deps_2(;\n            deps = PkgId[],\n            weakdeps = PkgId[],\n            loaded_uuids = UUID[],\n            ignore = Symbol[],\n        )\n        @test isempty(result)\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1],\n            weakdeps = PkgId[],\n            loaded_uuids = UUID[dep1.uuid, dep2.uuid, dep3.uuid],\n            ignore = Symbol[],\n        )\n        @test isempty(result)\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1],\n            weakdeps = PkgId[],\n            loaded_uuids = UUID[dep2.uuid, dep3.uuid],\n            ignore = Symbol[:Dep1],\n        )\n        @test isempty(result)\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1],\n            weakdeps = PkgId[dep2],\n            loaded_uuids = UUID[dep1.uuid],\n            ignore = Symbol[],\n        )\n        @test isempty(result)\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1, dep2],\n            weakdeps = PkgId[dep2],\n            loaded_uuids = UUID[dep1.uuid],\n            ignore = Symbol[],\n        )\n        @test isempty(result)\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1, dep2],\n            weakdeps = PkgId[dep2],\n            loaded_uuids = UUID[],\n            ignore = Symbol[:Dep1],\n        )\n        @test isempty(result)\n    end\n    @testset \"failure\" begin\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1],\n            weakdeps = PkgId[],\n            loaded_uuids = UUID[],\n            ignore = Symbol[],\n        )\n        @test length(result) == 1\n        @test dep1 in result\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1],\n            weakdeps = PkgId[],\n            loaded_uuids = UUID[dep2.uuid, dep3.uuid],\n            ignore = Symbol[],\n        )\n        @test length(result) == 1\n        @test dep1 in result\n\n        result = find_stale_deps_2(;\n            deps = PkgId[dep1, dep2],\n            weakdeps = PkgId[],\n            loaded_uuids = UUID[dep3.uuid],\n            ignore = Symbol[:Dep1],\n        )\n        @test length(result) == 1\n        @test dep2 in result\n    end\nend\n\nwith_sample_pkgs() do\n    @testset \"Package without `deps`\" begin\n        pkg = AquaTesting.SAMPLE_PKG_BY_NAME[\"PkgWithoutDeps\"]\n        results = Aqua.find_stale_deps(pkg)\n        @test isempty(results)\n    end\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_unbound_args.jl",
    "content": "module TestUnboundArgs\n\ninclude(\"preamble.jl\")\n\nusing PkgUnboundArgs\n\n@testset begin\n    println(\"### Expected output START ###\")\n    results = @testtestset begin\n        Aqua.test_unbound_args(PkgUnboundArgs)\n    end\n    println(\"### Expected output END ###\")\n    @test length(results) == 1\n    @test results[1] isa Test.Fail\n\n    # It works with other tests:\n    Aqua.test_ambiguities(PkgUnboundArgs)\n    Aqua.test_undefined_exports(PkgUnboundArgs)\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_undefined_exports.jl",
    "content": "module TestUndefinedExports\n\ninclude(\"preamble.jl\")\n\nusing PkgWithUndefinedExports\n\n@testset begin\n    @test Aqua.undefined_exports(PkgWithUndefinedExports) ==\n          [Symbol(\"PkgWithUndefinedExports.undefined_name\")]\n    println(\"### Expected output START ###\")\n    results = @testtestset begin\n        Aqua.test_undefined_exports(PkgWithUndefinedExports)\n    end\n    println(\"### Expected output END ###\")\n    @test length(results) == 1\n    @test results[1] isa Test.Fail\n\n    # It works with other tests:\n    Aqua.test_ambiguities(PkgWithUndefinedExports)\n    Aqua.test_unbound_args(PkgWithUndefinedExports)\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_undocumented_names.jl",
    "content": "module TestUndocumentedNames\n\ninclude(\"preamble.jl\")\n\nimport PkgWithUndocumentedNames\nimport PkgWithUndocumentedNamesInSubmodule\nimport PkgWithoutUndocumentedNames\n\n# Pass\nresults = @testtestset begin\n    Aqua.test_undocumented_names(PkgWithoutUndocumentedNames)\nend\nif VERSION >= v\"1.11\"\n    @test length(results) == 1\n    @test results[1] isa Test.Pass\nelse\n    @test length(results) == 0\nend\n\n# Fail\nprintln(\"### Expected output START ###\")\nresults = @testtestset begin\n    Aqua.test_undocumented_names(PkgWithUndocumentedNames)\nend\nprintln(\"### Expected output END ###\")\nif VERSION >= v\"1.11\"\n    @test length(results) == 1\n    @test results[1] isa Test.Fail\nelse\n    @test length(results) == 0\nend\n\nprintln(\"### Expected output START ###\")\nresults = @testtestset begin\n    Aqua.test_undocumented_names(PkgWithUndocumentedNamesInSubmodule)\nend\nprintln(\"### Expected output END ###\")\nif VERSION >= v\"1.11\"\n    @test length(results) == 1\n    @test results[1] isa Test.Fail\nelse\n    @test length(results) == 0\nend\n\n# Broken\nprintln(\"### Expected output START ###\")\nresults = @testtestset begin\n    Aqua.test_undocumented_names(PkgWithUndocumentedNames; broken = true)\nend\nprintln(\"### Expected output END ###\")\nif VERSION >= v\"1.11\"\n    @test length(results) == 1\n    @test results[1] isa Test.Broken\nelse\n    @test length(results) == 0\nend\n\nend  # module\n"
  },
  {
    "path": "test/test_utils.jl",
    "content": "module TestUtils\n\nusing Aqua: askwargs\nusing Test\n\n@testset \"askwargs\" begin\n    @test_throws ArgumentError(\"expect `true`\") askwargs(false)\n    @test askwargs(true) === NamedTuple()\n    @test askwargs(()) === NamedTuple()\n    @test askwargs((a = 1,)) === (a = 1,)\nend\n\nend  # module\n"
  }
]