Full Code of JuliaTesting/Aqua.jl for AI

master cdc679fb3809 cached
85 files
111.2 KB
34.2k tokens
1 requests
Download .txt
Repository: JuliaTesting/Aqua.jl
Branch: master
Commit: cdc679fb3809
Files: 85
Total size: 111.2 KB

Directory structure:
gitextract_3yp6x4rr/

├── .JuliaFormatter.toml
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── DocPreviewCleanup.yml
│       ├── TagBot.yml
│       ├── code-style.yml
│       ├── docs.yml
│       ├── enforce-labels.yml
│       └── test.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── Project.toml
├── README.md
├── docs/
│   ├── Project.toml
│   ├── changelog.jl
│   ├── instantiate.jl
│   ├── make.jl
│   └── src/
│       ├── ambiguities.md
│       ├── deps_compat.md
│       ├── exports.md
│       ├── index.md
│       ├── persistent_tasks.md
│       ├── piracies.md
│       ├── project_extras.md
│       ├── stale_deps.md
│       ├── test_all.md
│       ├── unbound_args.md
│       └── undocumented_names.md
├── src/
│   ├── Aqua.jl
│   ├── ambiguities.jl
│   ├── deps_compat.jl
│   ├── exports.jl
│   ├── persistent_tasks.jl
│   ├── piracies.jl
│   ├── precompile.jl
│   ├── project_extras.jl
│   ├── stale_deps.jl
│   ├── unbound_args.jl
│   ├── undocumented_names.jl
│   └── utils.jl
└── test/
    ├── pkgs/
    │   ├── AquaTesting.jl
    │   ├── PersistentTasks/
    │   │   ├── PersistentTask/
    │   │   │   ├── Project.toml
    │   │   │   └── src/
    │   │   │       └── PersistentTask.jl
    │   │   ├── TransientTask/
    │   │   │   ├── Project.toml
    │   │   │   └── src/
    │   │   │       └── TransientTask.jl
    │   │   └── UsesBoth/
    │   │       ├── Project.toml
    │   │       └── src/
    │   │           └── UsesBoth.jl
    │   ├── PiracyForeignProject/
    │   │   ├── Project.toml
    │   │   └── src/
    │   │       └── PiracyForeignProject.jl
    │   ├── PkgUnboundArgs.jl
    │   ├── PkgWithAmbiguities.jl
    │   ├── PkgWithUndefinedExports.jl
    │   ├── PkgWithUndocumentedNames.jl
    │   ├── PkgWithUndocumentedNamesInSubmodule.jl
    │   ├── PkgWithoutUndocumentedNames.jl
    │   └── sample/
    │       ├── PkgWithCompatibleTestProject/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithCompatibleTestProject.jl
    │       │   └── test/
    │       │       └── Project.toml
    │       ├── PkgWithIncompatibleTestProject/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithIncompatibleTestProject.jl
    │       │   └── test/
    │       │       └── Project.toml
    │       ├── PkgWithPostJulia12Support/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithPostJulia12Support.jl
    │       │   └── test/
    │       │       └── Project.toml
    │       ├── PkgWithoutDeps/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithoutDeps.jl
    │       │   └── test/
    │       │       └── .gitkeep
    │       └── PkgWithoutTestProject/
    │           ├── Project.toml
    │           ├── src/
    │           │   └── PkgWithoutTestProject.jl
    │           └── test/
    │               └── .gitkeep
    ├── preamble.jl
    ├── runtests.jl
    ├── test_ambiguities.jl
    ├── test_deps_compat.jl
    ├── test_exclude.jl
    ├── test_persistent_tasks.jl
    ├── test_piracy.jl
    ├── test_project_extras.jl
    ├── test_smoke.jl
    ├── test_stale_deps.jl
    ├── test_unbound_args.jl
    ├── test_undefined_exports.jl
    ├── test_undocumented_names.jl
    └── test_utils.jl

================================================
FILE CONTENTS
================================================

================================================
FILE: .JuliaFormatter.toml
================================================


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/DocPreviewCleanup.yml
================================================
name: Doc Preview Cleanup

on:
  pull_request:
    types: [closed]

jobs:
  doc-preview-cleanup:
    runs-on: ubuntu-slim
    permissions:
      contents: write
    steps:
      - name: Checkout gh-pages branch
        uses: actions/checkout@v6
        with:
          ref: gh-pages
      - name: Delete preview and history + push changes
        run: |
            if [ -d "previews/PR$PRNUM" ]; then
              git config user.name "Documenter.jl"
              git config user.email "documenter@juliadocs.github.io"
              git rm -rf "previews/PR$PRNUM"
              git commit -m "delete preview"
              git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
              git push --force origin gh-pages-new:gh-pages
            fi
        env:
            PRNUM: ${{ github.event.number }}

# copied from here:
# https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#gh-pages-Branch


================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot

on:
  issue_comment:
    types:
      - created
  workflow_dispatch:
    inputs:
      lookback:
        default: 3

jobs:
  TagBot:
    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
    runs-on: ubuntu-latest
    steps:
      - uses: JuliaRegistries/TagBot@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          ssh: ${{ secrets.SSH_KEY }}
          changelog: |
            {% if custom %}
            {{ custom }}
            {% endif %}

            The changes are documented in the [`CHANGELOG.md`](https://github.com/JuliaTesting/Aqua.jl/blob/{{ version }}/CHANGELOG.md) file.

            {% if previous_release %}
            [Diff since {{ previous_release }}]({{ compare_url }})
            {% endif %}


================================================
FILE: .github/workflows/code-style.yml
================================================
name: Code style

on:
  pull_request:

jobs:
  code-style:
    runs-on: ubuntu-slim
    steps:
      - uses: tkf/julia-code-style-suggesters@v1


================================================
FILE: .github/workflows/docs.yml
================================================
name: Documentation

on:
  push:
    branches:
      - master
      - 'release-*'
    tags: '*'
  pull_request:
  workflow_dispatch:

# needed to allow julia-actions/cache to delete old caches that it has created
permissions:
  actions: write
  contents: read

concurrency:
  # group by workflow and ref; the last slightly strange component ensures that for pull
  # requests, we limit to 1 concurrent job, but for the master branch we don't
  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }}
  # Cancel intermediate builds, but only if it is a pull request build.
  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

jobs:
  Documenter:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v6
      - uses: julia-actions/setup-julia@v2
        with:
          version: 1
      - uses: julia-actions/cache@v3
      - uses: julia-actions/julia-docdeploy@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DOCUMENTER_KEY: ${{ secrets.SSH_KEY }}


================================================
FILE: .github/workflows/enforce-labels.yml
================================================
name: Enforce PR labels

permissions:
  contents: read
on:
  pull_request:
    types: [labeled, unlabeled, opened, reopened, edited, synchronize]
jobs:
  enforce-labels:
    name: Check for blocking labels
    runs-on: ubuntu-latest
    timeout-minutes: 2
    steps:
    - uses: yogevbd/enforce-label-action@2.2.2
      with:
        REQUIRED_LABELS_ANY: "changelog: added,changelog: not needed,release"
        REQUIRED_LABELS_ANY_DESCRIPTION: "Select at least one label ['changelog: added','changelog: not needed','release']"
        BANNED_LABELS: "changelog: missing,DO NOT MERGE"
        BANNED_LABELS_DESCRIPTION: "A PR should not be merged with `DO NOT MERGE` or `changelog: missing` labels"


================================================
FILE: .github/workflows/test.yml
================================================
name: Run tests

on:
  push:
    branches:
      - master
      - 'release-*'
    tags: '*'
  pull_request:
  workflow_dispatch:

# needed to allow julia-actions/cache to delete old caches that it has created
permissions:
  actions: write
  contents: read

concurrency:
  # group by workflow and ref; the last slightly strange component ensures that for pull
  # requests, we limit to 1 concurrent job, but for the master branch we don't
  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }}
  # Cancel intermediate builds, but only if it is a pull request build.
  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

jobs:
  test:
    name: Test Julia ${{ matrix.julia-version }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      fail-fast: false
      matrix:
        os: ["ubuntu-latest"]
        julia-version:
          - '1.12-nightly'
          - '1.11'
          - '1.10'
          - '1.9'
          - '1.8'
          - '1.7'
          - '1.6'
          - 'nightly'
        include:
          - os: windows-latest
            julia-version: '1'
          - os: windows-latest
            julia-version: '1.6'
          - os: windows-latest
            julia-version: '1.12-nightly'
          - os: windows-latest
            julia-version: 'nightly'
          - os: macOS-latest
            julia-version: '1'
          - os: macOS-latest
            julia-version: '1.12-nightly'
          - os: macOS-latest
            julia-version: 'nightly'

    steps:
      - uses: actions/checkout@v6
        with:
          # For Codecov, we must also fetch the parent of the HEAD commit to
          # be able to properly deal with PRs / merges
          fetch-depth: 2
      - name: Setup julia
        uses: julia-actions/setup-julia@v2
        with:
          version: ${{ matrix.julia-version }}
      - uses: julia-actions/cache@v3
      - uses: julia-actions/julia-runtest@v1
        with:
          depwarn: error
      - uses: julia-actions/julia-processcoverage@v1
      - uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .gitignore
================================================
*.jl.*.cov
*.jl.cov
*.jl.mem
.DS_Store
/docs/build/
/docs/site/
/docs/src/release-notes.md
Manifest.toml


================================================
FILE: CHANGELOG.md
================================================
# Release notes

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Version [v1.0.0] - unreleased

### Changed

- Fix some world age issues in `test_ambiguities`. ([#366])
- The minimum supported julia version is increased to 1.6. ([#328])

## Version [v0.8.14] - 2025-08-04

### Changed

- Adapt to internal method table changes in Julia 1.12 and later. ([#344])

## Version [v0.8.13] - 2025-05-28

### Changed

- Adapt to internal method table changes in Julia 1.12 and later. ([#334])

## Version [v0.8.12] - 2025-05-05

### Changed

- 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])

## Version [v0.8.11] - 2025-02-06

### Changed

- Avoid deprecation warnings concerning `Base.isbindingresolved` with julia nightly. ([#322])

## Version [v0.8.10] - 2025-01-26

### Changed

- No longer call `@testset` for testsets that are skipped. ([#319])


## Version [v0.8.9] - 2024-10-15

### Changed

- Change `test_ambiguities` to only return ambiguities that happen in the target package. ([#309])


## Version [v0.8.8] - 2024-10-10

### Changed

- Improved the documentation of `test_persisten_tasks`. ([#297])


## Version [v0.8.7] - 2024-04-09

- Reverted [#285], which was originally released in [v0.8.6], but caused a regression. ([#287], [#288])


## Version [v0.8.6] - 2024-04-09

### Changed

- The output of `test_ambiguities` now gets printed to stderr instead of stdout. ([#281])


## Version [v0.8.5] - 2024-04-03

### Changed

- 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])
- Use [Changelog.jl](https://github.com/JuliaDocs/Changelog.jl) to generate the changelog, and add it to the documentation. ([#277], [#279])
- `test_project_extras` prints failures the same on all julia versions. In particular, 1.11 and nightly are no outliers. ([#275])


## Version [v0.8.4] - 2023-12-01

### Added

- `test_persistent_tasks` now accepts an optional `expr` to run in the precompile package. ([#255])
  + The `expr` option lets you test whether your precompile script leaves any dangling Tasks
  or Timers, which would make it unsafe to use as a dependency for downstream packages.


## Version [v0.8.3] - 2023-11-29

### Changed

- `test_persistent_tasks` is now less noisy. ([#256])
- Completely overhauled the documentation. Every test now has its dedicated page. ([#250])


## Version [v0.8.2] - 2023-11-16

### Changed

- `test_persistent_tasks` no longer clears the environment of the subtask. Instead, it modifies `LOAD_PATH` directly to make stdlibs work. ([#241])


## Version [v0.8.1] - 2023-11-16

### Changed

- `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])


## Version [v0.8.0] - 2023-11-15

### Added

- Two additions check whether packages might block precompilation on Julia 1.10 or higher: ([#174])
  + `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]
  + `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).

### Changed

- In `test_deps_compat`, the two subtests `check_extras` and `check_weakdeps` are now run by default. ([#202]) [BREAKING]
- `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]
- `test_ambiguities` now excludes the keyword sorter of all `exclude`d functions with keyword arguments as well. ([#203])
- `test_piracy` is renamed to `test_piracies`. ([#230]) [BREAKING]
- `test_ambiguities` and `test_piracies` now return issues in a defined order. This order may change in a patch release of Aqua.jl. ([#233])
- Improved the message for `test_project_extras` failures. ([#234])
- `test_deps_compat` now requires a compat entry for `julia` This can be disabling by setting `compat_julia = false`. ([#236]) [BREAKING]

### Removed

- `test_project_toml_formatting` has been removed. Thus, the kwarg `project_toml_formatting` to `test_all` no longer exists. ([#209]) [BREAKING]


## Version [v0.7.4] - 2023-10-24

### Added

- `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])

### Changed

- The docstring for `test_stale_deps` explains the situation with package extensions. ([#203])
- The logo of Aqua.jl has been updated. ([#128])


## Version [v0.7.3] - 2023-09-25

### Added

- `test_deps_compat` has a new kwarg `broken` to mark the test as broken using `Test.@test_broken`. ([#193])

### Fixed

- `test_piracy` no longer prints warnings for methods where the third argument is a `TypeVar`. ([#188])


## Version [v0.7.2] - 2023-09-19

### Changed

- `test_undefined_exports` additionally prints the modules of the undefined exports in the failure report. ([#177])


## Version [v0.7.1] - 2023-09-05

### Fixed

- `test_piracy` no longer reports type piracy in the kwsorter, i.e. `kwcall` should no longer appear in the report. ([#171])


## Version [v0.7.0] - 2023-08-29

### Added

- Installation and usage instructions to the documentation. ([#159])

### Changed

- `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]
- `test_piracy` considers more functions. Callables and qualified names are now also checked. ([#156]) [BREAKING]

### Fixed

- `test_ambiguities` prints less unnecessary whitespace. ([#158])
- `test_ambiguities` no longer hangs indefinitely when there are many ambiguities. ([#166])


## Version [v0.6.7] - 2023-09-19

### Changed

- `test_undefined_exports` additionally prints the modules of the undefined exports in the failure report. ([#177])

### Fixed

- `test_ambiguities` prints less unnecessary whitespace. ([#158])
- Fix `test_piracy` for some methods with arguments of custom subtypes of `Function`. ([#170])


## Version [v0.6.6] - 2023-08-24

### Fixed

- `test_ambiguities` no longer hangs indefinitely when there are many ambiguities. ([#166])


## Version [v0.6.5] - 2023-06-26

### Fixed

- Typo when calling kwargs. ([#153])


## Version [v0.6.4] - 2023-06-25

### Added

- `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])

### Changed

- Explanation of `test_unbound_args` in the docstring. ([#146])

### Fixed

- Callable objects with type parameters no longer error in `test_ambiguities`' kwarg `exclude`. ([#142])


## Version [v0.6.3] - 2023-06-05

### Changed

- 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])

### Fixed

- `test_deps_compat`'s kwarg `ignore` now works as intended. ([#130])
- Weakdeps are not reported as stale by `test_stale_deps` anymore. ([#135])


## Version [v0.6.2] - 2023-06-02

### Added

- `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])

### Changed

- `test_piracy` now prints the offending methods in a more readable way. ([#93])
- Extend `test_project_toml_formatting` to `docs/Project.toml`. ([#115])

### Fixed

- `test_stale_deps` no longer fails if any of the loaded packages prints during loading. ([#113])
- Clarified the error message of `test_unbound_args`. ([#103])
- Clarified the error message of `test_project_toml_formatting`. ([#122])


<!-- Links generated by Changelog.jl -->

[v0.6.2]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.2
[v0.6.3]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.3
[v0.6.4]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.4
[v0.6.5]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.5
[v0.6.6]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.6
[v0.6.7]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.6.7
[v0.7.0]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.0
[v0.7.1]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.1
[v0.7.2]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.2
[v0.7.3]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.3
[v0.7.4]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.7.4
[v0.8.0]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.0
[v0.8.1]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.1
[v0.8.2]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.2
[v0.8.3]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.3
[v0.8.4]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.4
[v0.8.5]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.5
[v0.8.6]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.6
[v0.8.7]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.7
[v0.8.8]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.8
[v0.8.9]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.9
[v0.8.10]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.10
[v0.8.11]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.11
[v0.8.12]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.12
[v0.8.13]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.13
[v0.8.14]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v0.8.14
[v1.0.0]: https://github.com/JuliaTesting/Aqua.jl/releases/tag/v1.0.0
[#93]: https://github.com/JuliaTesting/Aqua.jl/issues/93
[#103]: https://github.com/JuliaTesting/Aqua.jl/issues/103
[#113]: https://github.com/JuliaTesting/Aqua.jl/issues/113
[#115]: https://github.com/JuliaTesting/Aqua.jl/issues/115
[#122]: https://github.com/JuliaTesting/Aqua.jl/issues/122
[#124]: https://github.com/JuliaTesting/Aqua.jl/issues/124
[#128]: https://github.com/JuliaTesting/Aqua.jl/issues/128
[#130]: https://github.com/JuliaTesting/Aqua.jl/issues/130
[#131]: https://github.com/JuliaTesting/Aqua.jl/issues/131
[#135]: https://github.com/JuliaTesting/Aqua.jl/issues/135
[#140]: https://github.com/JuliaTesting/Aqua.jl/issues/140
[#142]: https://github.com/JuliaTesting/Aqua.jl/issues/142
[#144]: https://github.com/JuliaTesting/Aqua.jl/issues/144
[#146]: https://github.com/JuliaTesting/Aqua.jl/issues/146
[#153]: https://github.com/JuliaTesting/Aqua.jl/issues/153
[#156]: https://github.com/JuliaTesting/Aqua.jl/issues/156
[#158]: https://github.com/JuliaTesting/Aqua.jl/issues/158
[#159]: https://github.com/JuliaTesting/Aqua.jl/issues/159
[#166]: https://github.com/JuliaTesting/Aqua.jl/issues/166
[#170]: https://github.com/JuliaTesting/Aqua.jl/issues/170
[#171]: https://github.com/JuliaTesting/Aqua.jl/issues/171
[#174]: https://github.com/JuliaTesting/Aqua.jl/issues/174
[#177]: https://github.com/JuliaTesting/Aqua.jl/issues/177
[#188]: https://github.com/JuliaTesting/Aqua.jl/issues/188
[#193]: https://github.com/JuliaTesting/Aqua.jl/issues/193
[#200]: https://github.com/JuliaTesting/Aqua.jl/issues/200
[#202]: https://github.com/JuliaTesting/Aqua.jl/issues/202
[#203]: https://github.com/JuliaTesting/Aqua.jl/issues/203
[#209]: https://github.com/JuliaTesting/Aqua.jl/issues/209
[#215]: https://github.com/JuliaTesting/Aqua.jl/issues/215
[#230]: https://github.com/JuliaTesting/Aqua.jl/issues/230
[#233]: https://github.com/JuliaTesting/Aqua.jl/issues/233
[#234]: https://github.com/JuliaTesting/Aqua.jl/issues/234
[#236]: https://github.com/JuliaTesting/Aqua.jl/issues/236
[#240]: https://github.com/JuliaTesting/Aqua.jl/issues/240
[#241]: https://github.com/JuliaTesting/Aqua.jl/issues/241
[#250]: https://github.com/JuliaTesting/Aqua.jl/issues/250
[#255]: https://github.com/JuliaTesting/Aqua.jl/issues/255
[#256]: https://github.com/JuliaTesting/Aqua.jl/issues/256
[#272]: https://github.com/JuliaTesting/Aqua.jl/issues/272
[#275]: https://github.com/JuliaTesting/Aqua.jl/issues/275
[#277]: https://github.com/JuliaTesting/Aqua.jl/issues/277
[#279]: https://github.com/JuliaTesting/Aqua.jl/issues/279
[#281]: https://github.com/JuliaTesting/Aqua.jl/issues/281
[#285]: https://github.com/JuliaTesting/Aqua.jl/issues/285
[#287]: https://github.com/JuliaTesting/Aqua.jl/issues/287
[#288]: https://github.com/JuliaTesting/Aqua.jl/issues/288
[#297]: https://github.com/JuliaTesting/Aqua.jl/issues/297
[#309]: https://github.com/JuliaTesting/Aqua.jl/issues/309
[#313]: https://github.com/JuliaTesting/Aqua.jl/issues/313
[#319]: https://github.com/JuliaTesting/Aqua.jl/issues/319
[#322]: https://github.com/JuliaTesting/Aqua.jl/issues/322
[#328]: https://github.com/JuliaTesting/Aqua.jl/issues/328
[#334]: https://github.com/JuliaTesting/Aqua.jl/issues/334
[#344]: https://github.com/JuliaTesting/Aqua.jl/issues/344


================================================
FILE: LICENSE
================================================
Copyright (c) 2019 Takafumi Arakaki

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
JULIA:=julia

default: help

.PHONY: default changelog docs docs-instantiate generate_badge generate_favicon help test

docs-instantiate:
	${JULIA} docs/instantiate.jl

changelog: docs-instantiate
	${JULIA} --project=docs docs/changelog.jl

docs: docs-instantiate
	${JULIA} --project=docs docs/make.jl

generate_badge:
	SVG_BASE64=$(shell base64 -w 0 docs/src/assets/logo.svg); \
	curl -o "badge.svg" "https://img.shields.io/badge/tested_with-Aqua.jl-05C3DD.svg?logo=data:image/svg+xml;base64,$$SVG_BASE64"

generate_favicon:
	convert -background none docs/src/assets/logo.svg -resize 256x256 -gravity center -extent 256x256 logo.png
	convert logo.png -define icon:auto-resize=256,64,48,32,16 docs/src/assets/favicon.ico
	rm logo.png

test:
	${JULIA} --project -e 'using Pkg; Pkg.test()'

help:
	@echo "The following make commands are available:"
	@echo " - make changelog: update all links in CHANGELOG.md's footer"
	@echo " - make docs: build the documentation"
	@echo " - make generate_badge: generate the Aqua.jl badge"
	@echo " - make generate_favicon: generate the Aqua.jl favicon"
	@echo " - make test: run the tests"


================================================
FILE: Project.toml
================================================
name = "Aqua"
uuid = "4c88cf16-eb10-579e-8560-4a9242c79595"
authors = ["Takafumi Arakaki <aka.tkf@gmail.com> and contributors"]
version = "1.0.0-DEV"

[deps]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
Pkg = "1.6"
PrecompileTools = "1"
Test = "<0.0.1, 1"
julia = "1.6"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]


================================================
FILE: README.md
================================================
# Aqua.jl: *A*uto *QU*ality *A*ssurance for Julia packages

[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliatesting.github.io/Aqua.jl/stable)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliatesting.github.io/Aqua.jl/dev)
[![GitHub Actions](https://github.com/JuliaTesting/Aqua.jl/workflows/Run%20tests/badge.svg)](https://github.com/JuliaTesting/Aqua.jl/actions?query=workflow%3ARun+tests)
[![Codecov](https://codecov.io/gh/JuliaTesting/Aqua.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaTesting/Aqua.jl)
[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)

Aqua.jl provides functions to run a few automatable checks for Julia packages:

* There are no method ambiguities.
* There are no undefined `export`s.
* There are no unbound type parameters.
* There are no stale dependencies listed in `Project.toml`.
* Check that test target of the root project `Project.toml` and test project (`test/Project.toml`) are consistent.
* Check that all external packages listed in `deps` have corresponding `compat` entries.
* There are no "obvious" type piracies.
* The package does not create any persistent Tasks that might block precompilation of dependencies.

See more in the [documentation](https://juliatesting.github.io/Aqua.jl/).

For a detailed list of changes please refer to the [changelog](CHANGELOG.md).

## Setup

Please 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.

## Badge

You can add the following line in README.md to include Aqua.jl badge:

```markdown
[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)
```

which is rendered as

> [![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)

================================================
FILE: docs/Project.toml
================================================
[deps]
Changelog = "5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

[compat]
Changelog = "1"
Documenter = "1"


================================================
FILE: docs/changelog.jl
================================================
using Changelog

Changelog.generate(
    Changelog.CommonMark(),
    joinpath(@__DIR__, "..", "CHANGELOG.md");
    repo = "JuliaTesting/Aqua.jl",
)


================================================
FILE: docs/instantiate.jl
================================================
# This script can be used to quickly instantiate the docs/Project.toml environment.
using Pkg, TOML

package_directory = joinpath(@__DIR__, "..")
docs_directory = isempty(ARGS) ? @__DIR__() : joinpath(pwd(), ARGS[1])
cd(docs_directory) do
    Pkg.activate(docs_directory)
    Pkg.develop(PackageSpec(path = package_directory))
    Pkg.instantiate()

    # Remove Aqua again from docs/Project.toml
    lines = readlines(joinpath(docs_directory, "Project.toml"))
    open(joinpath(docs_directory, "Project.toml"), "w") do io
        for line in lines
            if line == "Aqua = \"4c88cf16-eb10-579e-8560-4a9242c79595\""
                continue
            end
            println(io, line)
        end
    end
end


================================================
FILE: docs/make.jl
================================================
using Documenter, Aqua, Changelog

# Generate a Documenter-friendly changelog from CHANGELOG.md
Changelog.generate(
    Changelog.Documenter(),
    joinpath(@__DIR__, "..", "CHANGELOG.md"),
    joinpath(@__DIR__, "src", "release-notes.md");
    repo = "JuliaTesting/Aqua.jl",
)

makedocs(;
    sitename = "Aqua.jl",
    format = Documenter.HTML(;
        repolink = "https://github.com/JuliaTesting/Aqua.jl",
        assets = ["assets/favicon.ico"],
        size_threshold_ignore = ["release-notes.md"],
    ),
    authors = "Takafumi Arakaki",
    modules = [Aqua],
    pages = [
        "Home" => "index.md",
        "Tests" => [
            "test_all.md",
            "ambiguities.md",
            "unbound_args.md",
            "exports.md",
            "project_extras.md",
            "stale_deps.md",
            "deps_compat.md",
            "piracies.md",
            "persistent_tasks.md",
            "undocumented_names.md",
        ],
        "release-notes.md",
    ],
)

deploydocs(; repo = "github.com/JuliaTesting/Aqua.jl", push_preview = true)


================================================
FILE: docs/src/ambiguities.md
================================================
# Ambiguities

Method ambiguities are cases where multiple methods are applicable to a given set of arguments, without having a most specific method.

## Examples
One easy example is the following:
```@repl
f(x::Int, y::Integer) = 1
f(x::Integer, y::Int) = 2

println(f(1, 2))
```
This will throw an `MethodError` because both methods are equally specific. The solution is to add a third method:
```julia
f(x::Int, y::Int) = ? # `?` is dependent on the use case, most times it will be `1` or `2`
```

## [Test function](@id test_ambiguities)

```@docs
Aqua.test_ambiguities
```


================================================
FILE: docs/src/deps_compat.md
================================================
# Compat entries

In your `Project.toml` you can (and should) use compat entries to specify
with which versions of Julia and your dependencies your package is compatible with.
This is important to ease the installation and upgrade of your package for users,
and to keep everything working in the case of breaking changes in Julia or your dependencies.

For more details, see the [Pkg docs](https://julialang.github.io/Pkg.jl/v1/compatibility/).

## [Test function](@id test_deps_compat)

```@docs
Aqua.test_deps_compat
```


================================================
FILE: docs/src/exports.md
================================================
# Undefined exports

## [Test function](@id test_undefined_exports)

```@docs
Aqua.test_undefined_exports
```


================================================
FILE: docs/src/index.md
================================================
# Aqua.jl: *A*uto *QU*ality *A*ssurance for Julia packages

Aqua.jl provides functions to run a few automatable checks for Julia packages:

* There are no method ambiguities.
* There are no undefined `export`s.
* There are no unbound type parameters.
* There are no stale dependencies listed in `Project.toml`.
* Check that test target of the root project `Project.toml` and test project (`test/Project.toml`) are consistent.
* Check that all external packages listed in `deps` have corresponding `compat` entries.
* There are no "obvious" type piracies.
* The package does not create any persistent Tasks that might block precompilation of dependencies.

## Quick usage

Call `Aqua.test_all(YourPackage)` from the REPL, e.g.,

```julia
using YourPackage
using Aqua
Aqua.test_all(YourPackage)
```

## How to add Aqua.jl...

### ...as a test dependency?

There are two ways to add Aqua.jl as a test dependency to your package.
To avoid breaking tests when a new Aqua.jl version is released, it is
recommended to add a version bound for Aqua.jl.

 1. In `YourPackage/test/Project.toml`, add Aqua.jl to `[dep]` and `[compat]` sections, like
    ```toml
    [deps]
    Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
    Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

    [compat]
    Aqua = "0.8"
    ```

 2. In `YourPackage/Project.toml`, add Aqua.jl to `[compat]` and `[extras]` section and the `test` target, like
    ```toml
    [compat]
    Aqua = "0.8"

    [extras]
    Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
    Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

    [targets]
    test = ["Aqua", "Test"]
    ```

If your package supports Julia pre-1.2, you need to use the second approach,
although you can use both approaches at the same time.

!!! warning
    In normal use, `Aqua.jl` should not be added to `[deps]` in `YourPackage/Project.toml`!

### ...to your tests?
It is recommended to create a separate file `YourPackage/test/Aqua.jl` that gets included in `YourPackage/test/runtests.jl`
with either

```julia
using Aqua
Aqua.test_all(YourPackage)
```
or some fine-grained checks with options, e.g.,

```julia
using Aqua

@testset "Aqua.jl" begin
  Aqua.test_all(
    YourPackage;
    ambiguities=(exclude=[SomePackage.some_function], broken=true),
    stale_deps=(ignore=[:SomePackage],),
    deps_compat=(ignore=[:SomeOtherPackage],),
    piracies=false,
  )
end
```
Note, that for all tests with no explicit options provided, the default options are used.

For more details on the options, see the respective functions [here](@ref test_all).

## Examples
The following is a small selection of packages that use Aqua.jl:
- [GAP.jl](https://github.com/oscar-system/GAP.jl)
- [Hecke.jl](https://github.com/thofma/Hecke.jl)
- [Oscar.jl](https://github.com/oscar-system/Oscar.jl)

## Badge

You can add the following line in README.md to include Aqua.jl badge:

```markdown
[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)
```

which is rendered as

[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)



================================================
FILE: docs/src/persistent_tasks.md
================================================
# Persistent Tasks

## Motivation

Julia 1.10 and higher wait for all running `Task`s to finish
before writing out the precompiled (cached) version of the package.
One consequence is that a package that launches
`Task`s in its `__init__` function may precompile successfully,
but block precompilation of any packages that depend on it.

The symptom of this problem is a message
```
◐ MyPackage: Waiting for background task / IO / timer. Interrupt to inspect...
```
that may appear during precompilation, with that precompilation process
"hanging" until you press Ctrl-C.

Aqua has checks to determine whether your package *causes* this problem.
Conversely, if you're a *victim* of this problem, it also has tools to help you
determine which of your dependencies is causing the problem.

## Example

Let's create a dummy package, `PkgA`, that launches a persistent `Task`:

```julia
module PkgA
const t = Ref{Timer}()   # used to prevent the Timer from being garbage-collected
__init__() = t[] = Timer(0.1; interval=1)   # create a persistent `Timer` `Task`
end
```

`PkgA` will precompile successfully, because `PkgA.__init__()` does not
run when `PkgA` is precompiled. However,

```julia
module PkgB
using PkgA
end
```

fails to precompile: `using PkgA` runs `PkgA.__init__()`, which
leaves the `Timer` `Task` running, and that causes precompilation
of `PkgB` to hang.

Without Aqua's tests, the developers of `PkgA` might not realize that their
package is essentially unusable with any other package.

## Checking for persistent tasks

Running all of Aqua's tests will automatically check whether your package falls
into this trap. In addition, there are ways to manually run (or tweak) this
specific test.

### Manually running the persistent-tasks check

[`Aqua.test_persistent_tasks(MyPackage)`](@ref) will check whether `MyPackage` blocks
precompilation for any packages that depend on it.

### Using an `expr` to check more than just `__init__`

By default, `Aqua.test_persistent_tasks` only checks whether a package's
`__init__` function leaves persistent tasks running. To check whether other
package functions leave persistent tasks running, pass a quoted expression:

```julia
Aqua.test_persistent_tasks(MyPackage; expr = quote
    # Code to run after loading MyPackage
    server = MyPackage.start_server()
    MyPackage.stop_server!(server)  # ideally, this this should cleanly shut everything down. Does it?
end)
```

Here is an example test with a dummy `expr` which will obviously fail, because it's explicitly
spawning a Task that never dies.
```@repl
using Aqua
Aqua.test_persistent_tasks(Aqua, expr = quote
    Threads.@spawn while true sleep(0.5) end
end)
```

## How the test works

This test works by launching a Julia process that tries to precompile a
dummy package similar to `PkgB` above, modified to signal back to Aqua when
`PkgA` has finished loading. The test fails if the gap between loading `PkgA`
and finishing precompilation exceeds time `tmax`.

## How to fix failing packages

Often, the easiest fix is to modify the `__init__` function to check whether the
Julia process is precompiling some other package; if so, don't launch the
persistent `Task`s.

```julia
function __init__()
    # Other setup code here
    if ccall(:jl_generating_output, Cint, ()) == 0   # if we're not precompiling...
        # launch persistent tasks here
    end
end
```

In more complex cases, you may need to modify the task to support a clean
shutdown. For example, if you have a `Task` that runs a never-terminating
`while` loop, you could change

```
    while true
        ⋮
    end
```

to

```
    while task_should_run[]
        ⋮
    end
```

where

```
const task_should_run = Ref(true)
```

is a global constant in your module. Setting `task_should_run[] = false` from
outside that `while` loop will cause it to terminate on its next iteration,
allowing the `Task` to finish.

## Additional information

[Julia's devdocs](https://docs.julialang.org/en/v1/devdocs/precompile_hang/)
also discuss this issue.

## [Test functions](@id test_persistent_tasks)

```@docs
Aqua.test_persistent_tasks
Aqua.find_persistent_tasks_deps
```


================================================
FILE: docs/src/piracies.md
================================================
# Type piracy

Type piracy is a term used to describe adding methods to a foreign function
with only foreign arguments.
This is considered bad practice because it can cause unexpected behavior
when the function is called, in particular, it can change the behavior of
one of your dependencies depending on if your package is loaded or not.
This makes it hard to reason about the behavior of your code, and may
introduce bugs that are hard to track down.

See [Julia documentation](https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-type-piracy) for more information about type piracy.

## Examples

Say that `PkgA` is foreign, and let's look at the different ways that `PkgB` extends its function `bar`.

```julia
module PkgA
    struct C end
    bar(x::C) = 42
    bar(x::Vector) = 43
end

module PkgB 
    import PkgA: bar, C
    struct D end
    bar(x::C) = 1
    bar(xs::D...) = 2
    bar(x::Vector{<:D}) = 3
    bar(x::Vector{D}) = 4 # slightly bad (may cause invalidations)
    bar(x::Union{C,D}) = 5 # slightly bad (a change in PkgA may turn it into piracy)
    #                        (for example changing bar(x::C) = 1 to bar(x::Union{C,Int}) = 1)
end
```

The following cases are enumerated by the return values in the example above:
1. This is the worst case of type piracy. The value of `bar(C())` can be
   either `1` or `42` and will depend on whether `PkgB` is loaded or not.
2. This is also a bad case of type piracy. `bar()` throws a `MethodError` with
   only `PkgA` available, and returns `2` with `PkgB` loaded. `PkgA` may add
   a method for `bar()` that takes no arguments in the future, and then this
   is equivalent to case 1.
3. This is a moderately bad case of type piracy. `bar(Union{}[])` returns `3`
   when `PkgB` is loaded, and `43` when `PkgB` is not loaded, although neither
   of the occurring types are defined in `PkgB`. This case is not as bad as
   cases 1 and 2, because it is only about behavior around `Union{}`, which has
   no instances.
4. Depending on ones understanding of type piracy, this could be considered piracy
   as well. In particular, this may cause invalidations.
5. This is a slightly bad case of type piracy. In the current form, `bar(C())`
   returns `42` as the dispatch on `Union{C,D}` is less specific. However, a
   future change in `PkgA` may change this behavior, e.g. by changing `bar(x::C)`
   to `bar(x::Union{C,Int})` the call `bar(C())` would become ambiguous.

!!! note
    The test function below currently only checks for cases 1 and 2.

## [Test function](@id test_piracies)

```@docs
Aqua.test_piracies
```


================================================
FILE: docs/src/project_extras.md
================================================
# Project.toml extras

There 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)):
1. Add the test-only dependencies to the `[extras]` section of your `Project.toml` file
   and use a test target.
2. Add the test-only dependencies to the `[deps]` section of your `test/Project.toml` file.
   This is only available in Julia 1.2 and later.

This test checks checks that, in case you use both methods, the set of test-only dependencies
is the same in both ways.

## [Test function](@id test_project_extras)

```@docs
Aqua.test_project_extras
```


================================================
FILE: docs/src/stale_deps.md
================================================
# Stale dependencies

## [Test function](@id test_stale_deps)

```@docs
Aqua.test_stale_deps
```


================================================
FILE: docs/src/test_all.md
================================================
# [Test everything](@id test_all)

This test runs most of the other tests in this module.
The defaults should be fine for most packages.
If 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)

```@docs
Aqua.test_all
```


================================================
FILE: docs/src/unbound_args.md
================================================
# Unbound Type Parameters

An unbound type parameter is a type parameter with a `where`,
that does not occur in the signature of some dispatch of the method.

## Examples

The following methods each have `T` as an unbound type parameter:

```@repl unbound
f(x::Int) where {T} = do_something(x)
g(x::T...) where {T} = println(T)
```

In the cases of `f` above, the unbound type parameter `T` is neither
present in the signature of the methods nor as a bound of another type parameter.
Here, the type parameter `T` can be removed without changing any semantics.

For signatures with `Vararg` (cf. `g` above), the type parameter is unbound for the 
zero-argument case (e.g. `g()`).

```@repl unbound
g(1.0, 2.0)
g(1)
g()
```

A possible fix would be to replace `g` by two methods.

```@repl unbound2
g() = println(Int)  # Defaults to `Int`
g(x1::T, x2::T...) where {T} = println(T)
g(1.0, 2.0)
g(1)
g()
```

## [Test function](@id test_unbound_args)

```@docs
Aqua.test_unbound_args
```


================================================
FILE: docs/src/undocumented_names.md
================================================
# Undocumented names

## [Test function](@id test_undocumented_names)

```@docs
Aqua.test_undocumented_names
```


================================================
FILE: src/Aqua.jl
================================================
module Aqua

using Base: Docs, PkgId, UUID
using Pkg: Pkg, TOML, PackageSpec
using Pkg.Types: VersionSpec, semver_spec
using Test


include("utils.jl")
include("ambiguities.jl")
include("unbound_args.jl")
include("exports.jl")
include("project_extras.jl")
include("stale_deps.jl")
include("deps_compat.jl")
include("piracies.jl")
include("persistent_tasks.jl")
include("undocumented_names.jl")

"""
    test_all(testtarget::Module)

Run the following tests on the module `testtarget`:

* [`test_ambiguities([testtarget])`](@ref test_ambiguities)
* [`test_unbound_args(testtarget)`](@ref test_unbound_args)
* [`test_undefined_exports(testtarget)`](@ref test_undefined_exports)
* [`test_project_extras(testtarget)`](@ref test_project_extras)
* [`test_stale_deps(testtarget)`](@ref test_stale_deps)
* [`test_deps_compat(testtarget)`](@ref test_deps_compat)
* [`test_piracies(testtarget)`](@ref test_piracies)
* [`test_persistent_tasks(testtarget)`](@ref test_persistent_tasks)
* [`test_undocumented_names(testtarget)`](@ref test_undocumented_names)

The keyword argument `\$x` (e.g., `ambiguities`) can be used to
control whether or not to run `test_\$x` (e.g., `test_ambiguities`).
If `test_\$x` supports keyword arguments, a `NamedTuple` can also be
passed to `\$x` to specify the keyword arguments for `test_\$x`.

# Keyword Arguments
- `ambiguities = true`
- `unbound_args = true`
- `undefined_exports = true`
- `project_extras = true`
- `stale_deps = true`
- `deps_compat = true`
- `piracies = true`
- `persistent_tasks = true`
- `undocumented_names = false`
"""
function test_all(
    testtarget::Module;
    ambiguities = true,
    unbound_args = true,
    undefined_exports = true,
    project_extras = true,
    stale_deps = true,
    deps_compat = true,
    piracies = true,
    persistent_tasks = true,
    undocumented_names = false,
)
    if ambiguities !== false
        @testset "Method ambiguity" begin
            test_ambiguities([testtarget]; askwargs(ambiguities)...)
        end
    end
    if unbound_args !== false
        @testset "Unbound type parameters" begin
            test_unbound_args(testtarget; askwargs(unbound_args)...)
        end
    end
    if undefined_exports !== false
        @testset "Undefined exports" begin
            test_undefined_exports(testtarget; askwargs(undefined_exports)...)
        end
    end
    if project_extras !== false
        @testset "Compare Project.toml and test/Project.toml" begin
            isempty(askwargs(project_extras)) || error("Keyword arguments not supported")
            test_project_extras(testtarget)
        end
    end
    if stale_deps !== false
        @testset "Stale dependencies" begin
            test_stale_deps(testtarget; askwargs(stale_deps)...)
        end
    end
    if deps_compat !== false
        @testset "Compat bounds" begin
            test_deps_compat(testtarget; askwargs(deps_compat)...)
        end
    end
    if piracies !== false
        @testset "Piracy" begin
            test_piracies(testtarget; askwargs(piracies)...)
        end
    end
    if persistent_tasks !== false
        @testset "Persistent tasks" begin
            test_persistent_tasks(testtarget; askwargs(persistent_tasks)...)
        end
    end
    if undocumented_names !== false
        @testset "Undocumented names" begin
            isempty(askwargs(undocumented_names)) ||
                error("Keyword arguments not supported")
            test_undocumented_names(testtarget; askwargs(undocumented_names)...)
        end
    end
end

include("precompile.jl")

end # module


================================================
FILE: src/ambiguities.jl
================================================
"""
    test_ambiguities(package::Union{Module, PkgId})
    test_ambiguities(packages::Vector{Union{Module, PkgId}})

Test that there is no method ambiguities in given package(s).

It calls `Test.detect_ambiguities` in a separated clean process to avoid
false-positives.

# Keyword Arguments
- `broken::Bool = false`: If true, it uses `@test_broken` instead of
  `@test` and shortens the error message.
- `color::Union{Bool, Nothing} = nothing`: Enable/disable colorful
  output if a `Bool`.  `nothing` (default) means to inherit the
  setting in the current process.
- `exclude::AbstractVector = []`: A vector of functions or types to be
  excluded from ambiguity testing.  A function means to exclude _all_
  its methods.  A type means to exclude _all_ its methods of the
  callable (sometimes also called "functor") and the constructor.
  That is to say, `MyModule.MyType` means to ignore ambiguities between
  `(::MyType)(x, y::Int)` and `(::MyType)(x::Int, y)`.
- `recursive::Bool = true`: Passed to `Test.detect_ambiguities`.
  Note that the default here (`true`) is different from
  `detect_ambiguities`.  This is for testing ambiguities in methods
  defined in all sub-modules.
- Other keyword arguments such as `imported` and `ambiguous_bottom`
  are passed to `Test.detect_ambiguities` as-is.
"""
test_ambiguities(packages; kwargs...) = _test_ambiguities(aspkgids(packages); kwargs...)

const ExcludeSpec = Pair{Base.PkgId,String}

rootmodule(x::Type) = rootmodule(parentmodule(x))
rootmodule(m::Module) = Base.require(PkgId(m))  # this handles Base/Core well

# get the Type associated with x
normalize_exclude_obj(@nospecialize x) = x isa Type ? x : typeof(x)

function normalize_exclude(@nospecialize x)
    x = normalize_exclude_obj(x)
    return Base.PkgId(rootmodule(x)) => join((fullname(parentmodule(x))..., string(nameof(x))), ".")
end

function getexclude((pkgid, name)::ExcludeSpec)
    nameparts = Symbol.(split(name, "."))
    m = Base.require(pkgid)
    for name in nameparts
        m = getproperty(m, name)
    end
    return normalize_exclude_obj(m)
end

function normalize_and_check_exclude(exclude::AbstractVector)
    exspecs = ExcludeSpec[normalize_exclude(exspec) for exspec in exclude]
    for (i, exspec) in enumerate(exspecs)
        if getexclude(exspec) != normalize_exclude_obj(exclude[i])
            error("Name `$(exspec[2])` is resolved to a different object.")
        end
    end
    return exspecs::Vector{ExcludeSpec}
end

function reprexclude(exspecs::Vector{ExcludeSpec})
    itemreprs = map(exspecs) do (pkgid, name)
        string("(", reprpkgid(pkgid), " => ", repr(name), ")")
    end
    return string("Aqua.ExcludeSpec[", join(itemreprs, ", "), "]")
end

function _test_ambiguities(packages::Vector{PkgId}; broken::Bool = false, kwargs...)
    num_ambiguities, strout, strerr =
        _find_ambiguities(packages; skipdetails = broken, kwargs...)

    print(stderr, strerr)
    print(stdout, strout)

    if broken
        @test_broken iszero(num_ambiguities)
    else
        @test iszero(num_ambiguities)
    end
end

function _find_ambiguities(
    packages::Vector{PkgId};
    skipdetails::Bool = false,
    color::Union{Bool,Nothing} = nothing,
    exclude::AbstractVector = [],
    # Options to be passed to `Test.detect_ambiguities`:
    detect_ambiguities_options...,
)
    packages_repr = reprpkgids(collect(packages))
    options_repr = checked_repr((; recursive = true, detect_ambiguities_options...))
    exclude_repr = reprexclude(normalize_and_check_exclude(exclude))

    # Ambiguity test is run inside a clean process.
    # https://github.com/JuliaLang/julia/issues/28804
    code = """
    $(Base.load_path_setup_code())
    using Aqua
    Aqua.test_ambiguities_impl(
        $packages_repr,
        $options_repr,
        $exclude_repr,
        $skipdetails,
    ) || exit(1)
    """
    cmd = Base.julia_cmd()
    if something(color, Base.JLOptions().color == 1)
        cmd = `$cmd --color=yes`
    end
    cmd = `$cmd --startup-file=no -e $code`

    mktemp() do outfile, out
        mktemp() do errfile, err
            succ = success(pipeline(cmd; stdout = out, stderr = err))
            strout = read(outfile, String)
            strerr = read(errfile, String)
            num_ambiguities = if succ
                0
            else
                reg_match = match(r"(\d+) ambiguities found", strerr)

                reg_match === nothing && error(
                    "Failed to parse output of `detect_ambiguities`.\nThe stdout was:\n" *
                    strout *
                    "\n\nThe stderr was:\n" *
                    strerr,
                )

                parse(Int, reg_match.captures[1]::AbstractString)
            end
            return num_ambiguities, strout, strerr
        end
    end
end

function reprpkgids(packages::Vector{PkgId})
    packages_repr = sprint() do io
        println(io, '[')
        for pkg in packages
            println(io, reprpkgid(pkg))
        end
        println(io, ']')
    end
    @assert Base.eval(Main, Meta.parse(packages_repr)) == packages
    return packages_repr
end

function reprpkgid(pkg::PkgId)
    name = pkg.name
    uuid = pkg.uuid
    if uuid === nothing
        return "Base.PkgId($(repr(name)))"
    end
    return "Base.PkgId(Base.UUID($(repr(uuid.value))), $(repr(name)))"
end

# try to extract the called function, or nothing if it is hard to analyze
function trygetft(m::Method)
    sig = Base.unwrap_unionall(m.sig)::DataType
    ft = sig.parameters[is_kwcall(sig) ? 3 : 1]
    ft = Base.unwrap_unionall(ft)
    if ft isa DataType && ft.name === Type.body.name
        ft = Base.unwrap_unionall(ft.parameters[1])
    end
    if ft isa DataType
        return ft.name.wrapper
    end
    return nothing # cannot exclude signatures with Union
end

function test_ambiguities_impl(
    packages::Vector{PkgId},
    options::NamedTuple,
    exspecs::Vector{ExcludeSpec},
    skipdetails::Bool,
)
    modules = map(Base.require, packages)
    @debug "Testing method ambiguities" modules
    ambiguities = detect_ambiguities(modules...; options...)

    if !isempty(exspecs)
        exclude_ft = Any[getexclude(spec) for spec in exspecs] # vector of Type objects
        ambiguities = filter(ambiguities) do (m1, m2)
            trygetft(m1) ∉ exclude_ft && trygetft(m2) ∉ exclude_ft
        end
    end

    sort!(ambiguities, by = (ms -> (ms[1].name, ms[2].name)))

    if !isempty(ambiguities)
        printstyled(
            stderr,
            "$(length(ambiguities)) ambiguities found. To get a list, set `broken = false`.\n";
            bold = true,
            color = Base.error_color(),
        )
    end
    if !skipdetails
        for (i, (m1, m2)) in enumerate(ambiguities)
            println(stderr, "Ambiguity #", i)
            println(stderr, m1)
            println(stderr, m2)
            @static if isdefined(Base, :morespecific)
                ambiguity_hint(stderr, m1, m2)
                println(stderr)
            end
            println(stderr)
        end
    end
    return isempty(ambiguities)
end

function ambiguity_hint(io::IO, m1::Method, m2::Method)
    # based on base/errorshow.jl#showerror_ambiguous
    # https://github.com/JuliaLang/julia/blob/v1.7.2/base/errorshow.jl#L327-L353
    sigfix = Any
    sigfix = typeintersect(m1.sig, sigfix)
    sigfix = typeintersect(m2.sig, sigfix)
    if isa(Base.unwrap_unionall(sigfix), DataType) && sigfix <: Tuple
        let sigfix = sigfix
            if all(m -> Base.morespecific(sigfix, m.sig), [m1, m2])
                print(io, "\nPossible fix, define\n  ")
                # Use `invokelatest` to not throw because of world age problems due to new types.
                invokelatest(Base.show_tuple_as_call, io, :function, sigfix)
            else
                println(io)
                print(
                    io,
                    """To resolve the ambiguity, try making one of the methods more specific, or 
                    adding a new method more specific than any of the existing applicable methods.""",
                )
            end
        end
    end
end


================================================
FILE: src/deps_compat.jl
================================================
"""
    Aqua.test_deps_compat(package)

Test that the `Project.toml` of `package` has a `compat` entry for
each package listed under `deps` and for `julia`.

# Arguments
- `packages`: a top-level `Module`, a `Base.PkgId`, or a collection of
  them.

# Keyword Arguments
## Test choosers
- `check_julia = true`: If true, additionally check for a compat entry for "julia".
- `check_extras = true`: If true, additionally check "extras". A NamedTuple
  can be used to pass keyword arguments with test options (see below).
- `check_weakdeps = true`: If true, additionally check "weakdeps". A NamedTuple
  can be used to pass keyword arguments with test options (see below).

## Test options
If these keyword arguments are set directly, they only apply to the standard test
for "deps". To apply them to "extras" and "weakdeps", pass them as a NamedTuple
to the corresponding `check_\$x` keyword argument.
- `broken::Bool = false`: If true, it uses `@test_broken` instead of
  `@test` for "deps".
- `ignore::Vector{Symbol}`: names of dependent packages to be ignored.
"""
function test_deps_compat(
    pkg::PkgId;
    check_julia = true,
    check_extras = true,
    check_weakdeps = true,
    kwargs...,
)
    if check_julia !== false
        @testset "julia" begin
            isempty(askwargs(check_julia)) || error("Keyword arguments not supported")
            test_julia_compat(pkg)
        end
    end
    @testset "$pkg deps" begin
        test_deps_compat(pkg, "deps"; kwargs...)
    end
    if check_extras !== false
        @testset "$pkg extras" begin
            test_deps_compat(pkg, "extras"; askwargs(check_extras)...)
        end
    end
    if check_weakdeps !== false
        @testset "$pkg weakdeps" begin
            test_deps_compat(pkg, "weakdeps"; askwargs(check_weakdeps)...)
        end
    end
end

function test_deps_compat(pkg::PkgId, deps_type::String; broken::Bool = false, kwargs...)
    result = find_missing_deps_compat(pkg, deps_type; kwargs...)
    if broken
        @test_broken isempty(result)
    else
        @test isempty(result)
    end
end

# Remove with next breaking version
function test_deps_compat(packages::Vector{<:Union{Module,PkgId}}; kwargs...)
    @testset "$pkg" for pkg in packages
        test_deps_compat(pkg; kwargs...)
    end
end

function test_deps_compat(mod::Module; kwargs...)
    test_deps_compat(aspkgid(mod); kwargs...)
end

function test_julia_compat(pkg::PkgId; broken::Bool = false)
    if broken
        @test_broken has_julia_compat(pkg)
    else
        @test has_julia_compat(pkg)
    end
end

function has_julia_compat(pkg::PkgId)
    root_project_path, found = root_project_toml(pkg)
    found || error("Unable to locate Project.toml")
    prj = TOML.parsefile(root_project_path)
    return has_julia_compat(prj)
end

function has_julia_compat(prj::Dict{String,Any})
    return "julia" in keys(get(prj, "compat", Dict{String,Any}()))
end

function find_missing_deps_compat(pkg::PkgId, deps_type::String = "deps"; kwargs...)
    root_project_path, found = root_project_toml(pkg)
    found || error("Unable to locate Project.toml")
    missing_compat =
        find_missing_deps_compat(TOML.parsefile(root_project_path), deps_type; kwargs...)

    if !isempty(missing_compat)
        printstyled(
            stderr,
            "$pkg does not declare a compat entry for the following $deps_type:\n";
            bold = true,
            color = Base.error_color(),
        )
        show(stderr, MIME"text/plain"(), missing_compat)
        println(stderr)
    end

    return missing_compat
end

function find_missing_deps_compat(
    prj::Dict{String,Any},
    deps_type::String;
    ignore::AbstractVector{Symbol} = Symbol[],
)
    deps = get(prj, deps_type, Dict{String,Any}())
    compat = get(prj, "compat", Dict{String,Any}())

    missing_compat = sort!(
        PkgId[
            d for d in map(d -> PkgId(UUID(last(d)), first(d)), collect(deps)) if
            !(d.name in keys(compat)) && !(d.name in String.(ignore))
        ];
        by = (pkg -> pkg.name),
    )
    return missing_compat
end


================================================
FILE: src/exports.jl
================================================
# avoid Base.isbindingresolved deprecation in https://github.com/JuliaLang/julia/pull/57253
function isbindingresolved(m::Module, s::Symbol)
    @static if VERSION >= v"1.12.0-"
        return true
    else
        return Base.isbindingresolved(m, s)
    end
end

function walkmodules(f, x::Module)
    f(x)
    for n in names(x; all = true)
        # `isdefined` and `getproperty` can trigger deprecation warnings
        if isbindingresolved(x, n) && !Base.isdeprecated(x, n)
            isdefined(x, n) || continue
            y = getproperty(x, n)
            if y isa Module && y !== x && parentmodule(y) === x
                walkmodules(f, y)
            end
        end
    end
end

function undefined_exports(m::Module)
    undefined = Symbol[]
    walkmodules(m) do x
        for n in names(x)
            isdefined(x, n) || push!(undefined, Symbol(join([fullname(x)...; n], '.')))
        end
    end
    return undefined
end

"""
    test_undefined_exports(m::Module; broken::Bool = false)

Test that all `export`ed names in `m` actually exist.

# Keyword Arguments
- `broken`: If true, it uses `@test_broken` instead of
  `@test` and shortens the error message.
"""
function test_undefined_exports(m::Module; broken::Bool = false)
    exports = undefined_exports(m)
    if broken
        if !isempty(exports)
            printstyled(
                stderr,
                "$(length(exports)) undefined exports detected. To get a list, set `broken = false`.\n";
                bold = true,
                color = Base.error_color(),
            )
        end
        @test_broken isempty(exports)
    else
        if !isempty(exports)
            printstyled(
                stderr,
                "Undefined exports detected:\n";
                bold = true,
                color = Base.error_color(),
            )
            show(stderr, MIME"text/plain"(), exports)
            println(stderr)
        end
        @test isempty(exports)
    end
end


================================================
FILE: src/persistent_tasks.jl
================================================
"""
    Aqua.test_persistent_tasks(package)

Test whether loading `package` creates persistent `Task`s
which may block precompilation of dependent packages.

See also [`Aqua.find_persistent_tasks_deps`](@ref).

If you provide an optional `expr`, this tests whether loading `package` and running `expr`
creates persistent `Task`s. For example, you might start and shutdown a web server, and
this will test that there aren't any persistent `Task`s.

On Julia version 1.9 and before, this test always succeeds.

# Arguments
- `package`: a top-level `Module` or `Base.PkgId`.

# Keyword Arguments
- `broken::Bool = false`: If true, it uses `@test_broken` instead of
  `@test`.
- `tmax::Real = 5`: the maximum time (in seconds) to wait after loading the
  package before forcibly shutting down the precompilation process (triggering
  a test failure).
- `expr::Expr = quote end`: An expression to run in the precompile package.

!!! note

    `Aqua.test_persistent_tasks(package)` creates a package with `package`
    as a dependency and runs the precompilation process.
    This requires that `package` is instantiable with the information in the
    `Project.toml` file alone.
    In particular, this will not work if some of `package`'s dependencies are `dev`ed
    packages or are given as a local path or a git repository in the `Manifest.toml`.
"""
function test_persistent_tasks(package::PkgId; broken::Bool = false, kwargs...)
    if broken
        @test_broken !has_persistent_tasks(package; kwargs...)
    else
        @test !has_persistent_tasks(package; kwargs...)
    end
end

function test_persistent_tasks(package::Module; kwargs...)
    test_persistent_tasks(PkgId(package); kwargs...)
end

function has_persistent_tasks(package::PkgId; expr::Expr = quote end, tmax = 10)
    root_project_path, found = root_project_toml(package)
    found || error("Unable to locate Project.toml")
    return !precompile_wrapper(root_project_path, tmax, expr)
end

"""
    Aqua.find_persistent_tasks_deps(package; kwargs...)

Test all the dependencies of `package` with [`Aqua.test_persistent_tasks`](@ref).

On Julia 1.10 and higher, it returns a list of all dependencies failing the test.
These are likely the ones blocking precompilation of your package.

Any `kwargs` are passed to [`Aqua.test_persistent_tasks`](@ref).
"""
function find_persistent_tasks_deps(package::PkgId; kwargs...)
    root_project_path, found = root_project_toml(package)
    found || error("Unable to locate Project.toml")
    prj = TOML.parsefile(root_project_path)
    deps = get(prj, "deps", Dict{String,Any}())
    filter!(deps) do (name, uuid)
        id = PkgId(UUID(uuid), name)
        return has_persistent_tasks(id; kwargs...)
    end
    return String[name for (name, _) in deps]
end

function find_persistent_tasks_deps(package::Module; kwargs...)
    find_persistent_tasks_deps(PkgId(package); kwargs...)
end

function precompile_wrapper(project, tmax, expr)
    @static if VERSION < v"1.10.0-"
        return true
    end
    prev_project = Base.active_project()::String
    isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(false)
    try
        pkgdir = dirname(project)
        pkgname = get(TOML.parsefile(project), "name", "")::String
        if isempty(pkgname)
            @error "Unable to locate package name in $project"
            return false
        end
        wrapperdir = tempname()
        wrappername, _ = only(Pkg.generate(wrapperdir; io = devnull))
        Pkg.activate(wrapperdir; io = devnull)
        Pkg.develop(PackageSpec(path = pkgdir); io = devnull)
        statusfile = joinpath(wrapperdir, "done.log")
        open(joinpath(wrapperdir, "src", wrappername * ".jl"), "w") do io
            println(
                io,
                """
module $wrappername
using $pkgname
$expr
# Signal Aqua from the precompilation process that we've finished loading the package
open("$(escape_string(statusfile))", "w") do io
    println(io, "done")
    flush(io)
end
end
""",
            )
        end
        # Precompile the wrapper package
        currently_precompiling = @ccall(jl_generating_output()::Cint) == 1
        cmd = if currently_precompiling
            # During precompilation we run a dummy command that just touches the
            # status file to keep things simple.
            code = """touch("$(escape_string(statusfile))")"""
            `$(Base.julia_cmd()) -e $code`
        else
            `$(Base.julia_cmd()) --project=$wrapperdir -e 'push!(LOAD_PATH, "@stdlib"); using Pkg; Pkg.precompile(; io = devnull)'`
        end

        cmd = pipeline(cmd; stdout, stderr)
        proc = run(cmd; wait = false)::Base.Process
        while !isfile(statusfile) && process_running(proc)
            sleep(0.5)
        end
        if !isfile(statusfile)
            @error "Unexpected error: $statusfile was not created, but precompilation exited"
            return false
        end
        # Check whether precompilation finishes in the required time
        t = time()
        while process_running(proc) && time() - t < tmax
            sleep(0.1)
        end
        success = !process_running(proc)
        if !success
            # SIGKILL to prevent julia from printing the SIG 15 handler, which can
            # misleadingly look like it's caused by an issue in the user's program.
            kill(proc, Base.SIGKILL)
        end
        return success
    finally
        isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(true)
        Pkg.activate(prev_project; io = devnull)
    end
end


================================================
FILE: src/piracies.jl
================================================
module Piracy

using ..Aqua: is_kwcall

using Test: is_in_mods

# based on Test/Test.jl#detect_ambiguities
# https://github.com/JuliaLang/julia/blob/v1.9.1/stdlib/Test/src/Test.jl#L1838-L1896
function all_methods(mods::Module...; skip_deprecated::Bool = true)
    meths = Method[]
    mods = collect(mods)::Vector{Module}

    function examine_def(m::Method)
        is_in_mods(m.module, true, mods) && push!(meths, m)
        nothing
    end

    examine(mt::Core.MethodTable) = Base.visit(examine_def, mt)

    if VERSION >= v"1.12-" && isdefined(Core, :methodtable)
        examine(Core.methodtable)
    elseif VERSION >= v"1.12-" && isdefined(Core, :GlobalMethods)
        # for all versions between JuliaLang/julia#58131 and JuliaLang/julia#59158
        # so just some 1.12 rcs and 1.13 DEVs
        examine(Core.GlobalMethods)
    else
        work = Base.loaded_modules_array()
        filter!(mod -> mod === parentmodule(mod), work) # some items in loaded_modules_array are not top modules (really just Base)
        while !isempty(work)
            mod = pop!(work)
            for name in names(mod; all = true)
                (skip_deprecated && Base.isdeprecated(mod, name)) && continue
                isdefined(mod, name) || continue
                f = Base.unwrap_unionall(getfield(mod, name))
                if isa(f, Module) && f !== mod && parentmodule(f) === mod && nameof(f) === name
                    push!(work, f)
                elseif isa(f, DataType) &&
                       isdefined(f.name, :mt) &&
                       parentmodule(f) === mod &&
                       nameof(f) === name &&
                       f.name.mt !== Symbol.name.mt &&
                       f.name.mt !== DataType.name.mt
                    examine(f.name.mt)
                end
            end
        end
        examine(Symbol.name.mt)
        examine(DataType.name.mt)
    end
    return meths
end

##################################
# Generic fallback for type parameters that are instances, like the 1 in
# Array{T, 1}
is_foreign(@nospecialize(x), pkg::Base.PkgId; treat_as_own) =
    is_foreign(typeof(x), pkg; treat_as_own = treat_as_own)

# Symbols can be used as type params - we assume these are unique and not
# piracy.  This implies that we have
#
#     julia> Aqua.Piracy.is_foreign(1, Base.PkgId(Aqua))
#     true
# 
#     julia> Aqua.Piracy.is_foreign(:hello, Base.PkgId(Aqua))
#     false
#
# and thus
#
#     julia> Aqua.Piracy.is_foreign(Val{1}, Base.PkgId(Aqua))
#     true
# 
#     julia> Aqua.Piracy.is_foreign(Val{:hello}, Base.PkgId(Aqua))
#     false
#
# Admittedly, this asymmetry is rather worrisome.  We do need to treat 1 foreign
# to consider `Vector{Char}` (i.e., `Array{Char,1}`) foreign.  This may suggest
# to treat the `Symbol` type foreign as well.  However, it means that we treat
# definition such as
#
#     ForeignModule.api_function(::Val{:MyPackageName}) = ...
# 
# as a type piracy even if this is actually the intended use-case (which is not
# a crazy API).  The symbol name may also come from `gensym`.  Since the aim of
# `Aqua.test_piracies` is to detect only "obvious" piracies, let us play on the
# safe side.
is_foreign(x::Symbol, pkg::Base.PkgId; treat_as_own) = false

is_foreign_module(mod::Module, pkg::Base.PkgId) = Base.PkgId(mod) != pkg

function is_foreign(@nospecialize(T::DataType), pkg::Base.PkgId; treat_as_own)
    params = T.parameters
    # For Type{Foo}, we consider it to originate from the same as Foo
    C = getfield(parentmodule(T), nameof(T))
    if C === Type
        @assert length(params) == 1
        return is_foreign(first(params), pkg; treat_as_own = treat_as_own)
    else
        # Both the type itself and all of its parameters must be foreign
        return !((C in treat_as_own)::Bool) &&
               is_foreign_module(parentmodule(T), pkg) &&
               all(param -> is_foreign(param, pkg; treat_as_own = treat_as_own), params)
    end
end

function is_foreign(@nospecialize(U::UnionAll), pkg::Base.PkgId; treat_as_own)
    # We do not consider extending Set{T} to be piracies, if T is not foreign.
    # Extending it goes against Julia style, but it's not piracies IIUC.
    is_foreign(U.body, pkg; treat_as_own = treat_as_own) &&
        is_foreign(U.var, pkg; treat_as_own = treat_as_own)
end

is_foreign(@nospecialize(T::TypeVar), pkg::Base.PkgId; treat_as_own) =
    is_foreign(T.ub, pkg; treat_as_own = treat_as_own)

# Before 1.7, Vararg was a UnionAll, so the UnionAll method will work
@static if VERSION >= v"1.7-"
    is_foreign(@nospecialize(T::Core.TypeofVararg), pkg::Base.PkgId; treat_as_own) =
        is_foreign(T.T, pkg; treat_as_own = treat_as_own)
end

function is_foreign(@nospecialize(U::Union), pkg::Base.PkgId; treat_as_own)
    # Even if Foo is local, overloading f(::Union{Foo, Int}) with foreign f is piracy.
    any(T -> is_foreign(T, pkg; treat_as_own = treat_as_own), Base.uniontypes(U))
end

function is_foreign_method(@nospecialize(U::Union), pkg::Base.PkgId; treat_as_own)
    # When installing a method for a union type, then we only consider it as
    # foreign if *all* parameters of the union are foreign, i.e. overloading
    # Union{Foo, Int}() is not piracy.
    all(T -> is_foreign(T, pkg; treat_as_own = treat_as_own), Base.uniontypes(U))
end

function is_foreign_method(@nospecialize(x::Any), pkg::Base.PkgId; treat_as_own)
    is_foreign(x, pkg; treat_as_own = treat_as_own)
end

function is_foreign_method(@nospecialize(T::DataType), pkg::Base.PkgId; treat_as_own)
    params = T.parameters
    # For Type{Foo}, we consider it to originate from the same as Foo
    C = getfield(parentmodule(T), nameof(T))
    if C === Type
        @assert length(params) == 1
        return is_foreign_method(first(params), pkg; treat_as_own = treat_as_own)
    end

    # fallback to general code
    return !((T in treat_as_own)::Bool) &&
           !(
               T <: Function &&
               isdefined(T, :instance) &&
               (T.instance in treat_as_own)::Bool
           ) &&
           is_foreign(T, pkg; treat_as_own = treat_as_own)
end


function is_pirate(meth::Method; treat_as_own = Union{Function,Type}[])
    method_pkg = Base.PkgId(meth.module)

    signature = Base.unwrap_unionall(meth.sig)

    function_type_index = 1
    if is_kwcall(meth.sig)
        # kwcall is a special case, since it is not a real function
        # but a wrapper around a function, the third parameter is the original
        # function, its positional arguments follow.
        function_type_index += 2
    end

    # the first parameter in the signature is the function type, and it
    # follows slightly other rules if it happens to be a Union type
    is_foreign_method(
        signature.parameters[function_type_index],
        method_pkg;
        treat_as_own = treat_as_own,
    ) || return false

    return all(
        param -> is_foreign(param, method_pkg; treat_as_own = treat_as_own),
        signature.parameters[function_type_index+1:end],
    )
end

function hunt(mod::Module; skip_deprecated::Bool = true, kwargs...)
    piracies = filter(all_methods(mod; skip_deprecated = skip_deprecated)) do method
        method.module === mod && is_pirate(method; kwargs...)
    end
    sort!(piracies, by = (m -> m.name))
    return piracies
end

end # module

"""
    test_piracies(m::Module)

Test that `m` does not commit type piracies.

# Keyword Arguments
- `broken::Bool = false`: If true, it uses `@test_broken` instead of
  `@test` and shortens the error message.
- `skip_deprecated::Bool = true`: If true, it does not check deprecated methods.
- `treat_as_own = Union{Function, Type}[]`: The types in this container
  are considered to be "owned" by the module `m`. This is useful for
  testing packages that deliberately commit some type piracies, e.g. modules
  adding higher-level functionality to a lightweight C-wrapper, or packages
  that are extending `StatsAPI.jl`.
"""
function test_piracies(m::Module; broken::Bool = false, kwargs...)
    v = Piracy.hunt(m; kwargs...)
    if broken
        if !isempty(v)
            printstyled(
                stderr,
                "$(length(v)) instances of possible type-piracy detected. To get a list, set `broken = false`.\n";
                bold = true,
                color = Base.error_color(),
            )
        end
        @test_broken isempty(v)
    else
        if !isempty(v)
            printstyled(
                stderr,
                "Possible type-piracy detected:\n";
                bold = true,
                color = Base.error_color(),
            )
            show(stderr, MIME"text/plain"(), v)
            println(stderr)
        end
        @test isempty(v)
    end
end


================================================
FILE: src/precompile.jl
================================================
using PrecompileTools: @compile_workload

# Create a minimal fake package to test. Needs to be at the top-level because
# it's a module.
module _FakePackage
export fake_function
fake_function() = 1
end

@compile_workload begin
    redirect_stdout(devnull) do
        test_all(
            _FakePackage;
            ambiguities = false,
            project_extras = false,
            stale_deps = false,
            deps_compat = false,
            persistent_tasks = false,
        )
    end

    # Explicitly precompile the tests that need a real package module
    precompile(test_ambiguities, (Vector{Module},))
    precompile(test_project_extras, (Module,))
    precompile(test_stale_deps, (Module,))
    precompile(test_deps_compat, (Module,))

    # Create a fake package directory for testing persistent_tasks. We go to
    # some effort to precompile this because it takes the longest due to Pkg
    # calls and running precompilation in a subprocess.
    mktempdir() do dir
        project_file = joinpath(dir, "Project.toml")
        write(
            project_file,
            """
name = "AquaFakePackage"
uuid = "5a23b2e7-8c45-4b1c-9d3f-7a6b4c8d9e0f"
version = "0.1.0"

[deps]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
Test = "1"
julia = "1"
""",
        )

        srcdir = joinpath(dir, "src")
        mkdir(srcdir)
        write(joinpath(srcdir, "AquaFakePackage.jl"), "module AquaFakePackage end")

        # The meat of the compilation latency comes from this function. Running
        # test_persistent_tasks() directly is more difficult because it takes a
        # Module and then gets the package directory, and we don't want to load
        # the fake package.
        precompile_wrapper(project_file, 10, quote end)
    end
end


================================================
FILE: src/project_extras.jl
================================================
"""
    test_project_extras(package::Union{Module, PkgId})
    test_project_extras(packages::Vector{Union{Module, PkgId}})

Check that test target of the root project and test project
(`test/Project.toml`) are consistent.

This is useful for supporting Julia < 1.2 while recording test-only dependency
compatibility in `test/Project.toml`.
"""
function test_project_extras(pkg::PkgId; kwargs...)
    msgs = analyze_project_extras(pkg; kwargs...)
    @test isempty(msgs)
end

# Remove with next breaking version
function test_project_extras(packages::Vector{<:Union{Module,PkgId}}; kwargs...)
    @testset "$pkg" for pkg in packages
        test_project_extras(pkg; kwargs...)
    end
end

function test_project_extras(mod::Module; kwargs...)
    test_project_extras(aspkgid(mod); kwargs...)
end

is_julia12_or_later(compat::AbstractString) = is_julia12_or_later(semver_spec(compat))
is_julia12_or_later(compat::VersionSpec) = isempty(compat ∩ semver_spec("1.0 - 1.1"))

function analyze_project_extras(pkg::PkgId)
    root_project_path, found = root_project_toml(pkg)
    found || error("Unable to locate Project.toml")

    test_project_path, found =
        project_toml_path(joinpath(dirname(root_project_path), "test"))
    found || return String[] # having no test/Project.toml is fine
    root_project = TOML.parsefile(root_project_path)
    test_project = TOML.parsefile(test_project_path)

    # Ignore root project's extras if only supporting julia 1.2 or later.
    # See: # https://julialang.github.io/Pkg.jl/v1/creating-packages/#Test-specific-dependencies-in-Julia-1.2-and-above-1
    julia_version = get(get(root_project, "compat", Dict{String,Any}()), "julia", nothing)
    isnothing(julia_version) && return String["Could not find `julia` compat."]
    is_julia12_or_later(julia_version) && return String[]

    # `extras_test_deps`: test-only dependencies according to Project.toml
    deps =
        PkgId[PkgId(UUID(v), k) for (k, v) in get(root_project, "deps", Dict{String,Any}())]
    target =
        Set{String}(get(get(root_project, "targets", Dict{String,Any}()), "test", String[]))
    extras_test_deps = setdiff(
        PkgId[
            PkgId(UUID(v), k) for
            (k, v) in get(root_project, "extras", Dict{String,Any}()) if k in target
        ],
        deps,
    )

    # `test_deps`: test-only dependencies according to test/Project.toml:
    test_deps = setdiff(
        PkgId[
            PkgId(UUID(v), k) for (k, v) in get(test_project, "deps", Dict{String,Any}())
        ],
        deps,
        [PkgId(UUID(root_project["uuid"]), root_project["name"])],
    )

    not_in_extras = setdiff(test_deps, extras_test_deps)
    not_in_test = setdiff(extras_test_deps, test_deps)
    if isempty(not_in_extras) && isempty(not_in_test)
        return String[]
    else
        msgs = String[]
        push!(
            msgs,
            "Root and test projects should be consistent for projects supporting Julia <= 1.1.",
        )
        if !isempty(not_in_extras)
            msg = "Test dependencies not in root project ($root_project_path):"
            for pkg in sort!(collect(not_in_extras); by = (pkg -> pkg.name))
                msg *= "\n\t$(pkg.name) [$(pkg.uuid)]"
            end
            push!(msgs, msg)
        end
        if !isempty(not_in_test)
            msg = "Dependencies not in test project ($test_project_path):"
            for pkg in sort!(collect(not_in_test); by = (pkg -> pkg.name))
                msg *= "\n\t$(pkg.name) [$(pkg.uuid)]"
            end
            push!(msgs, msg)
        end

        return msgs
    end
end


================================================
FILE: src/stale_deps.jl
================================================
"""
    Aqua.test_stale_deps(package; ignore::AbstractVector{Symbol} = Symbol[])

Test that `package` loads all dependencies listed in `Project.toml`.

Note that this does not imply that `package` loads the dependencies
directly, this can be achieved via transitivity as well.

!!! note "Weak dependencies and extensions"

    Due to the automatic loading of package extensions once all of
    their trigger dependencies are loaded, Aqua.jl can, by design of julia,
    not check if a package extension indeed loads all of its trigger
    dependencies using `import` or `using`.

!!! warning "Known bug"

    Currently, `Aqua.test_stale_deps` does not detect stale
    dependencies when they are in the sysimage. This is considered a
    bug and may be fixed in the future. Such a release is considered
    non-breaking.

# Arguments
- `packages`: a top-level `Module`, a `Base.PkgId`, or a collection of
  them.

# Keyword Arguments
- `ignore`: names of dependent packages to be ignored.
"""
function test_stale_deps(pkg::PkgId; kwargs...)
    stale_deps = find_stale_deps(pkg; kwargs...)
    @test isempty(stale_deps)
end

function test_stale_deps(mod::Module; kwargs...)
    test_stale_deps(aspkgid(mod); kwargs...)
end

# Remove in next breaking release
function test_stale_deps(packages::Vector{<:Union{Module,PkgId}}; kwargs...)
    @testset "$pkg" for pkg in packages
        test_stale_deps(pkg; kwargs...)
    end
end

function find_stale_deps(pkg::PkgId; ignore::AbstractVector{Symbol} = Symbol[])
    root_project_path, found = root_project_toml(pkg)
    found || error("Unable to locate Project.toml")

    prj = TOML.parsefile(root_project_path)
    deps::Vector{PkgId} =
        PkgId[PkgId(UUID(v), k) for (k::String, v::String) in get(prj, "deps", Dict())]
    weakdeps::Vector{PkgId} =
        PkgId[PkgId(UUID(v), k) for (k::String, v::String) in get(prj, "weakdeps", Dict())]

    marker = "_START_MARKER_"
    code = """
    $(Base.load_path_setup_code())
    Base.require($(reprpkgid(pkg)))
    print("$marker")
    for pkg in keys(Base.loaded_modules)
        pkg.uuid === nothing || println(pkg.uuid)
    end
    """
    cmd = Base.julia_cmd()
    output = read(`$cmd --startup-file=no --color=no -e $code`, String)
    pos = findfirst(marker, output)
    @assert !isnothing(pos)
    output = output[pos.stop+1:end]
    loaded_uuids = map(UUID, eachline(IOBuffer(output)))

    return find_stale_deps_2(;
        deps = deps,
        weakdeps = weakdeps,
        loaded_uuids = loaded_uuids,
        ignore = ignore,
    )
end

# Side-effect -free part of stale dependency analysis.
function find_stale_deps_2(;
    deps::AbstractVector{PkgId},
    weakdeps::AbstractVector{PkgId},
    loaded_uuids::AbstractVector{UUID},
    ignore::AbstractVector{Symbol},
)
    deps_uuids = [p.uuid for p in deps]
    pkgid_from_uuid = Dict(p.uuid => p for p in deps)

    stale_uuids = setdiff(deps_uuids, loaded_uuids)
    stale_pkgs = PkgId[pkgid_from_uuid[uuid] for uuid in stale_uuids]
    stale_pkgs = setdiff(stale_pkgs, weakdeps)
    stale_pkgs = PkgId[p for p in stale_pkgs if !(Symbol(p.name) in ignore)]

    return stale_pkgs
end


================================================
FILE: src/unbound_args.jl
================================================
"""
    test_unbound_args(m::Module; broken::Bool = false)

Test that all methods in `m` and its submodules do not have
unbound type parameters.

An unbound type parameter is a type parameter with a `where`, that does not
occur in the signature of some dispatch of the method.

# Keyword Arguments
- `broken`: If true, it uses `@test_broken` instead of
  `@test` and shortens the error message.
"""
function test_unbound_args(m::Module; broken::Bool = false)
    unbounds = detect_unbound_args_recursively(m)
    if broken
        if !isempty(unbounds)
            printstyled(
                stderr,
                "$(length(unbounds)) instances of unbound type parameters detected. To get a list, set `broken = false`.\n";
                bold = true,
                color = Base.error_color(),
            )
        end
        @test_broken isempty(unbounds)
    else
        if !isempty(unbounds)
            printstyled(
                stderr,
                "Unbound type parameters detected:\n";
                bold = true,
                color = Base.error_color(),
            )
            show(stderr, MIME"text/plain"(), unbounds)
            println(stderr)
        end

        @test isempty(unbounds)
    end
end

detect_unbound_args_recursively(m) = Test.detect_unbound_args(m; recursive = true)


================================================
FILE: src/undocumented_names.jl
================================================
"""
    test_undocumented_names(m::Module; broken::Bool = false)

Test that all public names in `m` and its recursive submodules have a docstring
(not including `m` itself).

!!! tip
    On all Julia versions, public names include the exported names.
    On Julia versions >= 1.11, public names also include the names annotated with the
    `public` keyword.

!!! warning
    When running this Aqua test in Julia versions before 1.11, it does nothing.
    Thus if you use continuous integration tests, make sure those are configured
    to use Julia >= 1.11 in order to benefit from this test.

# Keyword Arguments
- `broken`: If true, it uses `@test_broken` instead of
  `@test` and shortens the error message.
"""
function test_undocumented_names(m::Module; broken::Bool = false)
    @static if VERSION >= v"1.11"
        # exclude the module name itself because it has the README as auto-generated docstring (https://github.com/JuliaLang/julia/pull/39093)
        undocumented_names = Symbol[]
        walkmodules(m) do x
            append!(undocumented_names, Docs.undocumented_names(x))
        end
        undocumented_names = filter(n -> n != nameof(m), undocumented_names)
        if broken
            @test_broken isempty(undocumented_names)
        else
            @test isempty(undocumented_names)
        end
    else
        undocumented_names = Symbol[]
    end
    if !isempty(undocumented_names)
        printstyled(
            stderr,
            "Undocumented names detected:\n";
            bold = true,
            color = Base.error_color(),
        )
        !broken && show(stderr, MIME"text/plain"(), undocumented_names)
        println(stderr)
    end
end


================================================
FILE: src/utils.jl
================================================
askwargs(kwargs) = (; kwargs...)
function askwargs(flag::Bool)
    if !flag
        throw(ArgumentError("expect `true`"))
    end
    return NamedTuple()
end

aspkgids(pkg::Union{Module,PkgId}) = aspkgids([pkg])
aspkgids(packages) = mapfoldl(aspkgid, push!, packages, init = PkgId[])

aspkgid(pkg::PkgId) = pkg
function aspkgid(m::Module)
    if !ispackage(m)
        error("Non-package (non-toplevel) module is not supported. Got: $m")
    end
    return PkgId(m)
end
function aspkgid(name::Symbol)
    # Maybe `Base.depwarn()`
    return Base.identify_package(String(name))::PkgId
end

ispackage(m::Module) =
    if m in (Base, Core)
        true
    else
        parentmodule(m) == m
    end

function project_toml_path(dir)
    candidates = joinpath.(dir, ["Project.toml", "JuliaProject.toml"])
    i = findfirst(isfile, candidates)
    i === nothing && return candidates[1], false
    return candidates[i], true
end

function root_project_toml(pkg::PkgId)
    srcpath = Base.locate_package(pkg)
    srcpath === nothing && return "", false
    pkgpath = dirname(dirname(srcpath))
    root_project_path, found = project_toml_path(pkgpath)
    return root_project_path, found
end

module _TempModule end

eval_string(code::AbstractString) = include_string(_TempModule, code)

function checked_repr(obj)
    code = repr(obj)
    if !isequal(eval_string(code), obj)
        error("`$repr` is not `repr`-safe")
    end
    return code
end

function is_kwcall(signature::Type)
    @static if VERSION < v"1.9"
        signature = Base.unwrap_unionall(signature)::DataType
        try
            length(signature.parameters) >= 3 || return false
            signature <: Tuple{Function,Any,Any,Vararg} || return false
            (signature.parameters[3] isa DataType && signature.parameters[3] <: Type) ||
                isconcretetype(signature.parameters[3]) ||
                return false
            return signature.parameters[1] === Core.kwftype(signature.parameters[3])
        catch err
            @warn "Please open an issue on JuliaTesting/Aqua.jl for \"is_kwcall\" and the following data:" signature err
            return false
        end
    else
        return signature <: Tuple{typeof(Core.kwcall), Any, Any, Vararg}
    end
end


================================================
FILE: test/pkgs/AquaTesting.jl
================================================
module AquaTesting

using Base: PkgId, UUID
using Pkg
using Test

# Taken from Test/test/runtests.jl
mutable struct NoThrowTestSet <: Test.AbstractTestSet
    results::Vector
    NoThrowTestSet(desc) = new([])
end
Test.record(ts::NoThrowTestSet, t::Test.Result) = (push!(ts.results, t); t)
Test.finish(ts::NoThrowTestSet) = ts.results


macro testtestset(args...)
    @gensym TestSet
    expr = quote
        $TestSet = $NoThrowTestSet
        $Test.@testset($TestSet, $(args...))
    end
    esc(expr)
end


const SAMPLE_PKGIDS = [
    PkgId(UUID("1649c42c-2196-4c52-9963-79822cd6227b"), "PkgWithIncompatibleTestProject"),
    PkgId(UUID("6e4a843a-fdff-4fa3-bb5a-e4ae67826963"), "PkgWithCompatibleTestProject"),
    PkgId(UUID("7231ce0e-e308-4079-b49f-19e33cc3ac6e"), "PkgWithPostJulia12Support"),
    PkgId(UUID("8981f3dd-97fd-4684-8ec7-7b0c42f64e2e"), "PkgWithoutTestProject"),
    PkgId(UUID("3922d3f4-c8f6-c8a8-00da-60b44ed8eac6"), "PkgWithoutDeps"),
]

const SAMPLE_PKG_BY_NAME = Dict(pkg.name => pkg for pkg in SAMPLE_PKGIDS)

function with_sample_pkgs(f)
    sampledir = joinpath(@__DIR__, "sample")

    original_load_path = copy(LOAD_PATH)
    try
        pushfirst!(LOAD_PATH, sampledir)
        f()
    finally
        append!(empty!(LOAD_PATH), original_load_path)
    end
end

end  # module


================================================
FILE: test/pkgs/PersistentTasks/PersistentTask/Project.toml
================================================
name = "PersistentTask"
uuid = "e5c298b6-d81d-47aa-a9ed-5ea983e22986"


================================================
FILE: test/pkgs/PersistentTasks/PersistentTask/src/PersistentTask.jl
================================================
module PersistentTask

const t = Ref{Any}()
__init__() = t[] = Timer(0.1; interval = 1)   # create a persistent `Timer` `Task`

end # module PersistentTask


================================================
FILE: test/pkgs/PersistentTasks/TransientTask/Project.toml
================================================
name = "TransientTask"
uuid = "94ae9332-58b0-4342-989c-0a7e44abcca9"


================================================
FILE: test/pkgs/PersistentTasks/TransientTask/src/TransientTask.jl
================================================
module TransientTask

__init__() = Timer(0.1)   # create a transient `Timer` `Task`

end # module TransientTask


================================================
FILE: test/pkgs/PersistentTasks/UsesBoth/Project.toml
================================================
name = "UsesBoth"
uuid = "96f12b6e-60f8-43dc-b131-049a88a2f499"

[deps]
PersistentTask = "e5c298b6-d81d-47aa-a9ed-5ea983e22986"
TransientTask = "94ae9332-58b0-4342-989c-0a7e44abcca9"


================================================
FILE: test/pkgs/PersistentTasks/UsesBoth/src/UsesBoth.jl
================================================
module UsesBoth

using TransientTask
using PersistentTask

end # module UsesBoth


================================================
FILE: test/pkgs/PiracyForeignProject/Project.toml
================================================
name = "PiracyForeignProject"
uuid = "f592ac8b-a2e8-4dd0-be7a-e4053dab5b76"


================================================
FILE: test/pkgs/PiracyForeignProject/src/PiracyForeignProject.jl
================================================
module PiracyForeignProject

struct ForeignType end
struct ForeignParameterizedType{T} end

struct ForeignNonSingletonType
    x::Int
end

end


================================================
FILE: test/pkgs/PkgUnboundArgs.jl
================================================
module PkgUnboundArgs

# Putting it in a submodule to test that `recursive=true` is used.
module M25341
_totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = E
end
# `_totuple` is taken from
# https://github.com/JuliaLang/julia/blob/48634f9f8669e1dc1be0a1589cd5be880c04055a/test/ambiguous.jl#L257-L259

end  # module


================================================
FILE: test/pkgs/PkgWithAmbiguities.jl
================================================
module PkgWithAmbiguities

# 1 ambiguity
f(::Any, ::Int) = 1
f(::Int, ::Any) = 2
const num_ambs_f = 1

# 2 ambiguities:
#   1 for g
#   1 for Core.kwfunc(g)
g(::Any, ::Int; kw) = 1
g(::Int, ::Any; kw) = 2
const num_ambs_g = 2

abstract type AbstractType end
struct SingletonType <: AbstractType end

struct ConcreteType <: AbstractType
    x::Int
end

# 2 ambiguities
SingletonType(::Any, ::Any, ::Int) = 1
SingletonType(::Any, ::Int, ::Int) = 2
SingletonType(::Int, ::Any, ::Any) = 3

# 1 ambiguity
(::SingletonType)(::Any, ::Float64) = 1
(::SingletonType)(::Float64, ::Any) = 2

const num_ambs_SingletonType = 3

# 3 ambiguities
ConcreteType(::Any, ::Any, ::Int) = 1
ConcreteType(::Any, ::Int, ::Any) = 2
ConcreteType(::Int, ::Any, ::Any) = 3

# 1 ambiguity
(::ConcreteType)(::Any, ::Float64) = 1
(::ConcreteType)(::Float64, ::Any) = 2

const num_ambs_ConcreteType = 4

# 1 ambiguity
abstract type AbstractParameterizedType{T} end
struct ConcreteParameterizedType{T} <: AbstractParameterizedType{T} end
(::AbstractParameterizedType{T})(::Tuple{Tuple{Int}}) where {T} = 1
(::ConcreteParameterizedType)(::Tuple) = 2

const num_ambs_ParameterizedType = 1

end  # module


================================================
FILE: test/pkgs/PkgWithUndefinedExports.jl
================================================
module PkgWithUndefinedExports

export undefined_name

end  # module


================================================
FILE: test/pkgs/PkgWithUndocumentedNames.jl
================================================
module PkgWithUndocumentedNames

"""
    documented_function
"""
function documented_function end

function undocumented_function end

"""
    DocumentedStruct
"""
struct DocumentedStruct end

struct UndocumentedStruct end

export documented_function, DocumentedStruct
export undocumented_function, UndocumentedStruct

end  # module


================================================
FILE: test/pkgs/PkgWithUndocumentedNamesInSubmodule.jl
================================================
module PkgWithUndocumentedNamesInSubmodule

"""
    DocumentedStruct
"""
struct DocumentedStruct end

module SubModule

struct UndocumentedStruct end

end

end  # module


================================================
FILE: test/pkgs/PkgWithoutUndocumentedNames.jl
================================================
"""
    PkgWithoutUndocumentedNames
"""
module PkgWithoutUndocumentedNames

"""
    documented_function
"""
function documented_function end

"""
    DocumentedStruct
"""
struct DocumentedStruct end

export documented_function, DocumentedStruct

end  # module


================================================
FILE: test/pkgs/sample/PkgWithCompatibleTestProject/Project.toml
================================================
name = "PkgWithCompatibleTestProject"
uuid = "6e4a843a-fdff-4fa3-bb5a-e4ae67826963"

[compat]
julia = "1"

[extras]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Pkg", "Test"]


================================================
FILE: test/pkgs/sample/PkgWithCompatibleTestProject/src/PkgWithCompatibleTestProject.jl
================================================
module PkgWithCompatibleTestProject end


================================================
FILE: test/pkgs/sample/PkgWithCompatibleTestProject/test/Project.toml
================================================
[deps]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"


================================================
FILE: test/pkgs/sample/PkgWithIncompatibleTestProject/Project.toml
================================================
name = "PkgWithIncompatibleTestProject"
uuid = "1649c42c-2196-4c52-9963-79822cd6227b"

[compat]
julia = "1"

[extras]
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["REPL", "Test"]


================================================
FILE: test/pkgs/sample/PkgWithIncompatibleTestProject/src/PkgWithIncompatibleTestProject.jl
================================================
module PkgWithIncompatibleTestProject end


================================================
FILE: test/pkgs/sample/PkgWithIncompatibleTestProject/test/Project.toml
================================================
[deps]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"


================================================
FILE: test/pkgs/sample/PkgWithPostJulia12Support/Project.toml
================================================
name = "PkgWithPostJulia12Support"
uuid = "7231ce0e-e308-4079-b49f-19e33cc3ac6e"

[compat]
julia = "1.2"

[extras]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Pkg", "Test"]


================================================
FILE: test/pkgs/sample/PkgWithPostJulia12Support/src/PkgWithPostJulia12Support.jl
================================================
module PkgWithPostJulia12Support end


================================================
FILE: test/pkgs/sample/PkgWithPostJulia12Support/test/Project.toml
================================================
[deps]
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"


================================================
FILE: test/pkgs/sample/PkgWithoutDeps/Project.toml
================================================
name = "PkgWithoutDeps"
uuid = "3922d3f4-c8f6-c8a8-00da-60b44ed8eac6"

[compat]
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]


================================================
FILE: test/pkgs/sample/PkgWithoutDeps/src/PkgWithoutDeps.jl
================================================
module PkgWithoutDeps end


================================================
FILE: test/pkgs/sample/PkgWithoutDeps/test/.gitkeep
================================================


================================================
FILE: test/pkgs/sample/PkgWithoutTestProject/Project.toml
================================================
name = "PkgWithoutTestProject"
uuid = "8981f3dd-97fd-4684-8ec7-7b0c42f64e2e"

[compat]
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]


================================================
FILE: test/pkgs/sample/PkgWithoutTestProject/src/PkgWithoutTestProject.jl
================================================
module PkgWithoutTestProject end


================================================
FILE: test/pkgs/sample/PkgWithoutTestProject/test/.gitkeep
================================================


================================================
FILE: test/preamble.jl
================================================
let path = joinpath(@__DIR__, "pkgs")
    if path ∉ LOAD_PATH
        pushfirst!(LOAD_PATH, path)
    end
end

using Test
using Aqua
using AquaTesting: @testtestset, AquaTesting, with_sample_pkgs


================================================
FILE: test/runtests.jl
================================================
module TestAqua

using Test

@testset "$file" for file in sort([
    file for file in readdir(@__DIR__) if match(r"^test_.*\.jl$", file) !== nothing
])
    include(file)
end

end  # module


================================================
FILE: test/test_ambiguities.jl
================================================
module TestAmbiguities

include("preamble.jl")

@testset begin
    using PkgWithAmbiguities

    using PkgWithAmbiguities:
        num_ambs_f,
        num_ambs_g,
        num_ambs_SingletonType,
        num_ambs_ConcreteType,
        num_ambs_ParameterizedType
    total =
        num_ambs_f +
        num_ambs_g +
        num_ambs_SingletonType +
        num_ambs_ConcreteType +
        num_ambs_ParameterizedType

    function check_testcase(exclude, num_ambiguities::Int; broken::Bool = false)
        pkgids = Aqua.aspkgids([PkgWithAmbiguities, Core]) # include Core to find constructor ambiguities
        num_ambiguities_, strout, strerr = Aqua._find_ambiguities(pkgids; exclude = exclude)
        if broken
            @test_broken num_ambiguities_ == num_ambiguities
        else
            if num_ambiguities_ != num_ambiguities
                @show exclude
                println(strout)
                println(strerr)
            end
            @test num_ambiguities_ == num_ambiguities
        end
    end

    check_testcase([], total)

    # exclude just anything irrelevant, see #49
    check_testcase([convert], total)

    # exclude function
    check_testcase([PkgWithAmbiguities.f], total - num_ambs_f)

    # exclude function and kwsorter
    check_testcase([PkgWithAmbiguities.g], total - num_ambs_g)

    # exclude callables and constructors
    check_testcase([PkgWithAmbiguities.SingletonType], total - num_ambs_SingletonType)
    check_testcase([PkgWithAmbiguities.ConcreteType], total - num_ambs_ConcreteType)

    # exclude abstract supertype without callables and constructors
    check_testcase([PkgWithAmbiguities.AbstractType], total)

    # for ambiguities between abstract and concrete type callables, only one needs to be excluded
    check_testcase(
        [PkgWithAmbiguities.AbstractParameterizedType],
        total - num_ambs_ParameterizedType,
    )
    check_testcase(
        [PkgWithAmbiguities.ConcreteParameterizedType],
        total - num_ambs_ParameterizedType,
    )

    # exclude everything
    check_testcase(
        [
            PkgWithAmbiguities.f,
            PkgWithAmbiguities.g,
            PkgWithAmbiguities.SingletonType,
            PkgWithAmbiguities.ConcreteType,
            PkgWithAmbiguities.ConcreteParameterizedType,
        ],
        0,
    )


    # It works with other tests:
    Aqua.test_unbound_args(PkgWithAmbiguities)
    Aqua.test_undefined_exports(PkgWithAmbiguities)
end

end  # module


================================================
FILE: test/test_deps_compat.jl
================================================
module TestDepsCompat

include("preamble.jl")
using Aqua: find_missing_deps_compat, has_julia_compat

const DictSA = Dict{String,Any}

@testset "has_julia_compat" begin
    @test has_julia_compat(DictSA("compat" => DictSA("julia" => "1")))
    @test has_julia_compat(DictSA("compat" => DictSA("julia" => "1.0")))
    @test has_julia_compat(DictSA("compat" => DictSA("julia" => "1.6")))
    @test has_julia_compat(
        DictSA(
            "deps" => DictSA("PkgA" => "229717a1-0d13-4dfb-ba8f-049672e31205"),
            "compat" => DictSA("julia" => "1", "PkgA" => "1.0"),
        ),
    )

    @test !has_julia_compat(DictSA())
    @test !has_julia_compat(DictSA("compat" => DictSA()))
    @test !has_julia_compat(DictSA("compat" => DictSA("PkgA" => "1.0")))
    @test !has_julia_compat(
        DictSA(
            "deps" => DictSA("PkgA" => "229717a1-0d13-4dfb-ba8f-049672e31205"),
            "compat" => DictSA("PkgA" => "1.0"),
        ),
    )
end

@testset "find_missing_deps_compat" begin
    @testset "pass" begin
        result = find_missing_deps_compat(
            DictSA("deps" => DictSA(), "compat" => DictSA("julia" => "1")),
            "deps",
        )
        @test isempty(result)

        result = find_missing_deps_compat(
            DictSA(
                "deps" => DictSA("PkgA" => "229717a1-0d13-4dfb-ba8f-049672e31205"),
                "compat" => DictSA("julia" => "1", "PkgA" => "1.0"),
            ),
            "deps",
        )
        @test isempty(result)
        @testset "does not have `deps`" begin
            result = find_missing_deps_compat(DictSA(), "deps")
            @test isempty(result)
        end
    end
    @testset "failure" begin
        @testset "does not have `compat`" begin
            result = find_missing_deps_compat(
                DictSA("deps" => DictSA("PkgA" => "229717a1-0d13-4dfb-ba8f-049672e31205")),
                "deps",
            )
            @test length(result) == 1
            @test [pkg.name for pkg in result] == ["PkgA"]
        end

        @testset "does not specify `compat` for PkgA" begin
            result = find_missing_deps_compat(
                DictSA(
                    "deps" => DictSA("PkgA" => "229717a1-0d13-4dfb-ba8f-049672e31205"),
                    "compat" => DictSA("julia" => "1"),
                ),
                "deps",
            )
            @test length(result) == 1
            @test [pkg.name for pkg in result] == ["PkgA"]
        end

        @testset "does not specify `compat` for PkgB" begin
            result = find_missing_deps_compat(
                DictSA(
                    "deps" => DictSA(
                        "PkgA" => "229717a1-0d13-4dfb-ba8f-049672e31205",
                        "PkgB" => "3d97d89c-7c41-49ae-981c-14fe13cc7943",
                    ),
                    "compat" => DictSA("julia" => "1", "PkgA" => "1.0"),
                ),
                "deps",
            )
            @test length(result) == 1
            @test [pkg.name for pkg in result] == ["PkgB"]
        end

        @testset "does not specify `compat` for stdlib" begin
            result = find_missing_deps_compat(
                DictSA(
                    "deps" => DictSA(
                        "LinearAlgebra" => "37e2e46d-f89d-539d-b4ee-838fcccc9c8e",
                    ),
                    "compat" => DictSA("julia" => "1"),
                ),
                "deps",
            )
            @test length(result) == 1
            @test [pkg.name for pkg in result] == ["LinearAlgebra"]
        end
    end
end

end  # module


================================================
FILE: test/test_exclude.jl
================================================
module TestExclude

include("preamble.jl")
using Base: PkgId
using Aqua: getexclude, normalize_exclude, normalize_exclude_obj, normalize_and_check_exclude, rootmodule, reprexclude

@assert parentmodule(Tuple) === Core
@assert parentmodule(foldl) === Base
@assert parentmodule(Some) === Base
@assert parentmodule(Broadcast.Broadcasted) === Base.Broadcast
@assert rootmodule(Broadcast.Broadcasted) === Base

@testset "roundtrip" begin
    @testset for x in Any[
        foldl
        Some
        Tuple
        Broadcast.Broadcasted
        nothing
        Any
    ]
        @test getexclude(normalize_exclude(x)) === normalize_exclude_obj(x)
    end
    @test_throws ErrorException normalize_and_check_exclude(Any[Pair{Int}])

    @testset "$(repr(last(spec)))" for spec in [
        (PkgId(Base) => "Base.#foldl")
        (PkgId(Base) => "Base.Some")
        (PkgId(Core) => "Core.Tuple")
        (PkgId(Base) => "Base.Broadcast.Broadcasted")
        (PkgId(Core) => "Core.Nothing")
        (PkgId(Core) => "Core.Any")
    ]
        @test normalize_exclude(getexclude(spec)) === spec
    end
end

@testset "normalize_and_check_exclude" begin
    @testset "$i" for (i, exclude) in enumerate([Any[foldl], Any[foldl, Some], Any[foldl, Tuple]])
        local specs
        @test (specs = normalize_and_check_exclude(exclude)) isa Vector
        @test Base.include_string(@__MODULE__, reprexclude(specs)) == specs
    end
end

end  # module


================================================
FILE: test/test_persistent_tasks.jl
================================================
module TestPersistentTasks

include("preamble.jl")
using Base: PkgId, UUID
using Pkg: TOML

function getid(name)
    path = joinpath(@__DIR__, "pkgs", "PersistentTasks", name)
    if path ∉ LOAD_PATH
        pushfirst!(LOAD_PATH, path)
    end
    prj = TOML.parsefile(joinpath(path, "Project.toml"))
    return PkgId(UUID(prj["uuid"]), prj["name"])
end


@testset "PersistentTasks" begin
    @test !Aqua.has_persistent_tasks(getid("TransientTask"))

    result = Aqua.find_persistent_tasks_deps(getid("TransientTask"))
    @test result == []

    if Base.VERSION >= v"1.10-"
        @test Aqua.has_persistent_tasks(getid("PersistentTask"))

        result = Aqua.find_persistent_tasks_deps(getid("UsesBoth"))
        @test result == ["PersistentTask"]
    end
    filter!(str -> !occursin("PersistentTasks", str), LOAD_PATH)
end

@testset "test_persistent_tasks(expr)" begin
    if Base.VERSION >= v"1.10-"
        @test !Aqua.has_persistent_tasks(
            getid("TransientTask"),
            expr = quote
                fetch(Threads.@spawn nothing)
            end,
        )
        @test Aqua.has_persistent_tasks(getid("TransientTask"), expr = quote
            Threads.@spawn while true
                sleep(0.5)
            end
        end)
    end
end

end


================================================
FILE: test/test_piracy.jl
================================================
push!(LOAD_PATH, joinpath(@__DIR__, "pkgs", "PiracyForeignProject"))

baremodule PiracyModule

using PiracyForeignProject: ForeignType, ForeignParameterizedType, ForeignNonSingletonType

using Base:
    Base,
    Set,
    AbstractSet,
    Integer,
    Val,
    Vararg,
    Vector,
    Unsigned,
    UInt,
    String,
    Tuple,
    AbstractChar

struct Foo end
struct Bar{T<:AbstractSet{<:Integer}} end

# Not piracy: Function defined here
f(::Int, ::Union{String,Char}) = 1
f(::Int) = 2
Foo(::Int) = Foo()

# Not piracy: At least one argument is local
Base.findlast(::Foo, x::Int) = x + 1
Base.findlast(::Set{Foo}, x::Int) = x + 1
Base.findlast(::Type{Val{Foo}}, x::Int) = x + 1
Base.findlast(::Tuple{Vararg{Bar{Set{Int}}}}, x::Int) = x + 1
Base.findlast(::Val{:foo}, x::Int) = x + 1
Base.findlast(::ForeignParameterizedType{Foo}, x::Int) = x + 1

# Not piracy
const MyUnion = Union{Int,Foo}
MyUnion(x::Int) = x
MyUnion(; x::Int) = x

export MyUnion

# Piracy
Base.findfirst(::Set{Vector{Char}}, ::Int) = 1
Base.findfirst(::Union{Foo,Bar{Set{Unsigned}},UInt}, ::Tuple{Vararg{String}}) = 1
Base.findfirst(::AbstractChar, ::Set{T}) where {Int <: T <: Integer} = 1
(::ForeignType)(x::Int8) = x + 1
(::ForeignNonSingletonType)(x::Int8) = x + 1

# Piracy, but not for `ForeignType in treat_as_own`
Base.findmax(::ForeignType, x::Int) = x + 1
Base.findmax(::Set{Vector{ForeignType}}, x::Int) = x + 1
Base.findmax(::Union{Foo,ForeignType}, x::Int) = x + 1

# Piracy, but not for `ForeignParameterizedType in treat_as_own`
Base.findmin(::ForeignParameterizedType{Int}, x::Int) = x + 1
Base.findmin(::Set{Vector{ForeignParameterizedType{Int}}}, x::Int) = x + 1
Base.findmin(::Union{Foo,ForeignParameterizedType{Int}}, x::Int) = x + 1

end # PiracyModule

using Aqua: Piracy
using PiracyForeignProject: ForeignType, ForeignParameterizedType, ForeignNonSingletonType

# Get all methods - test length
meths = filter(Piracy.all_methods(PiracyModule)) do m
    m.module == PiracyModule
end

@test length(meths) ==
      2 + # Foo constructors
      1 + # Bar constructor
      2 + # f
      4 + # MyUnion (incl. kwcall)
      6 + # findlast
      3 + # findfirst
      1 + # ForeignType callable
      1 + # ForeignNonSingletonType callable
      3 + # findmax
      3   # findmin

# Test what is foreign
BasePkg = Base.PkgId(Base)
CorePkg = Base.PkgId(Core)
ThisPkg = Base.PkgId(PiracyModule)

@test Piracy.is_foreign(Int, BasePkg; treat_as_own = []) # from Core
@test !Piracy.is_foreign(Int, CorePkg; treat_as_own = []) # from Core
@test !Piracy.is_foreign(Set{Int}, BasePkg; treat_as_own = [])
@test !Piracy.is_foreign(Set{Int}, CorePkg; treat_as_own = [])

# Test what is pirate
pirates = Piracy.hunt(PiracyModule)
@test length(pirates) ==
      3 + # findfirst
      3 + # findmax
      3 + # findmin
      1 + # ForeignType callable
      1   # ForeignNonSingletonType callable
@test all(pirates) do m
    m.name in [:findfirst, :findmax, :findmin, :ForeignType, :ForeignNonSingletonType]
end

# Test what is pirate (with treat_as_own=[ForeignType])
pirates = Piracy.hunt(PiracyModule, treat_as_own = [ForeignType])
@test length(pirates) ==
      3 + # findfirst
      3 + # findmin
      1   # ForeignNonSingletonType callable
@test all(pirates) do m
    m.name in [:findfirst, :findmin, :ForeignNonSingletonType]
end

# Test what is pirate (with treat_as_own=[ForeignParameterizedType])
pirates = Piracy.hunt(PiracyModule, treat_as_own = [ForeignParameterizedType])
@test length(pirates) ==
      3 + # findfirst
      3 + # findmax
      1 + # ForeignType callable
      1   # ForeignNonSingletonType callable
@test all(pirates) do m
    m.name in [:findfirst, :findmax, :ForeignType, :ForeignNonSingletonType]
end

# Test what is pirate (with treat_as_own=[ForeignType, ForeignParameterizedType])
pirates = filter(
    m -> Piracy.is_pirate(m; treat_as_own = [ForeignType, ForeignParameterizedType]),
    meths,
)
@test length(pirates) ==
      3 + # findfirst
      1   # ForeignNonSingletonType callable
@test all(pirates) do m
    m.name in [:findfirst, :ForeignNonSingletonType]
end

# Test what is pirate (with treat_as_own=[Base.findfirst, Base.findmax])
pirates = Piracy.hunt(PiracyModule, treat_as_own = [Base.findfirst, Base.findmax])
@test length(pirates) ==
      3 + # findmin
      1 + # ForeignType callable
      1   # ForeignNonSingletonType callable
@test all(pirates) do m
    m.name in [:findmin, :ForeignType, :ForeignNonSingletonType]
end

# Test what is pirate (excluding a cover of everything)
pirates = filter(
    m -> Piracy.is_pirate(
        m;
        treat_as_own = [
            ForeignType,
            ForeignParameterizedType,
            ForeignNonSingletonType,
            Base.findfirst,
        ],
    ),
    meths,
)
@test length(pirates) == 0


================================================
FILE: test/test_project_extras.jl
================================================
module TestProjectExtras

include("preamble.jl")
using Aqua: is_julia12_or_later
using Base: PkgId, UUID

@testset "is_julia12_or_later" begin
    @test is_julia12_or_later("1.2")
    @test is_julia12_or_later("1.3")
    @test is_julia12_or_later("1.3, 1.4")
    @test is_julia12_or_later("1.3 - 1.4, 1.6")
    @test !is_julia12_or_later("1")
    @test !is_julia12_or_later("1.1")
    @test !is_julia12_or_later("1.0 - 1.1")
    @test !is_julia12_or_later("1.0 - 1.3")
end

with_sample_pkgs() do
    @testset "PkgWithIncompatibleTestProject" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithIncompatibleTestProject"]
        result = Aqua.analyze_project_extras(pkg)
        @test !isempty(result)
        @test any(
            msg -> occursin(
                "Root and test projects should be consistent for projects supporting Julia <= 1.1.",
                msg,
            ),
            result,
        )
        @test any(
            msg ->
                occursin("Test dependencies not in root project", msg) &&
                    occursin("Random [9a3f8284-a2c9-5f02-9a11-845980a1fd5c]", msg),
            result,
        )
        @test any(
            msg ->
                occursin("Dependencies not in test project", msg) &&
                    occursin("REPL [3fa0cd96-eef1-5676-8a61-b3b8758bbffb]", msg),
            result,
        )
        @test !any(msg -> occursin("Test [", msg), result)
    end

    @testset "PkgWithCompatibleTestProject" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithCompatibleTestProject"]
        result = Aqua.analyze_project_extras(pkg)
        @test isempty(result)
    end

    @testset "PkgWithPostJulia12Support" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithPostJulia12Support"]
        result = Aqua.analyze_project_extras(pkg)
        @test isempty(result)
    end

    @testset "PkgWithoutTestProject" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithoutTestProject"]
        result = Aqua.analyze_project_extras(pkg)
        @test isempty(result)
    end
end

end  # module


================================================
FILE: test/test_smoke.jl
================================================
module TestSmoke

using Aqua

# test defaults
Aqua.test_all(Aqua)

# test everything else
Aqua.test_all(
    Aqua;
    ambiguities = false,
    unbound_args = false,
    undefined_exports = false,
    project_extras = false,
    stale_deps = false,
    deps_compat = false,
    piracies = false,
    persistent_tasks = false,
)

end  # module


================================================
FILE: test/test_stale_deps.jl
================================================
module TestStaleDeps

include("preamble.jl")
using Base: PkgId, UUID
using Aqua: find_stale_deps_2

@testset "find_stale_deps_2" begin
    pkg = PkgId(UUID(42), "TargetPkg")

    dep1 = PkgId(UUID(1), "Dep1")
    dep2 = PkgId(UUID(2), "Dep2")
    dep3 = PkgId(UUID(3), "Dep3")

    @testset "pass" begin
        result = find_stale_deps_2(;
            deps = PkgId[],
            weakdeps = PkgId[],
            loaded_uuids = UUID[],
            ignore = Symbol[],
        )
        @test isempty(result)

        result = find_stale_deps_2(;
            deps = PkgId[dep1],
            weakdeps = PkgId[],
            loaded_uuids = UUID[dep1.uuid, dep2.uuid, dep3.uuid],
            ignore = Symbol[],
        )
        @test isempty(result)

        result = find_stale_deps_2(;
            deps = PkgId[dep1],
            weakdeps = PkgId[],
            loaded_uuids = UUID[dep2.uuid, dep3.uuid],
            ignore = Symbol[:Dep1],
        )
        @test isempty(result)

        result = find_stale_deps_2(;
            deps = PkgId[dep1],
            weakdeps = PkgId[dep2],
            loaded_uuids = UUID[dep1.uuid],
            ignore = Symbol[],
        )
        @test isempty(result)

        result = find_stale_deps_2(;
            deps = PkgId[dep1, dep2],
            weakdeps = PkgId[dep2],
            loaded_uuids = UUID[dep1.uuid],
            ignore = Symbol[],
        )
        @test isempty(result)

        result = find_stale_deps_2(;
            deps = PkgId[dep1, dep2],
            weakdeps = PkgId[dep2],
            loaded_uuids = UUID[],
            ignore = Symbol[:Dep1],
        )
        @test isempty(result)
    end
    @testset "failure" begin
        result = find_stale_deps_2(;
            deps = PkgId[dep1],
            weakdeps = PkgId[],
            loaded_uuids = UUID[],
            ignore = Symbol[],
        )
        @test length(result) == 1
        @test dep1 in result

        result = find_stale_deps_2(;
            deps = PkgId[dep1],
            weakdeps = PkgId[],
            loaded_uuids = UUID[dep2.uuid, dep3.uuid],
            ignore = Symbol[],
        )
        @test length(result) == 1
        @test dep1 in result

        result = find_stale_deps_2(;
            deps = PkgId[dep1, dep2],
            weakdeps = PkgId[],
            loaded_uuids = UUID[dep3.uuid],
            ignore = Symbol[:Dep1],
        )
        @test length(result) == 1
        @test dep2 in result
    end
end

with_sample_pkgs() do
    @testset "Package without `deps`" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithoutDeps"]
        results = Aqua.find_stale_deps(pkg)
        @test isempty(results)
    end
end

end  # module


================================================
FILE: test/test_unbound_args.jl
================================================
module TestUnboundArgs

include("preamble.jl")

using PkgUnboundArgs

@testset begin
    println("### Expected output START ###")
    results = @testtestset begin
        Aqua.test_unbound_args(PkgUnboundArgs)
    end
    println("### Expected output END ###")
    @test length(results) == 1
    @test results[1] isa Test.Fail

    # It works with other tests:
    Aqua.test_ambiguities(PkgUnboundArgs)
    Aqua.test_undefined_exports(PkgUnboundArgs)
end

end  # module


================================================
FILE: test/test_undefined_exports.jl
================================================
module TestUndefinedExports

include("preamble.jl")

using PkgWithUndefinedExports

@testset begin
    @test Aqua.undefined_exports(PkgWithUndefinedExports) ==
          [Symbol("PkgWithUndefinedExports.undefined_name")]
    println("### Expected output START ###")
    results = @testtestset begin
        Aqua.test_undefined_exports(PkgWithUndefinedExports)
    end
    println("### Expected output END ###")
    @test length(results) == 1
    @test results[1] isa Test.Fail

    # It works with other tests:
    Aqua.test_ambiguities(PkgWithUndefinedExports)
    Aqua.test_unbound_args(PkgWithUndefinedExports)
end

end  # module


================================================
FILE: test/test_undocumented_names.jl
================================================
module TestUndocumentedNames

include("preamble.jl")

import PkgWithUndocumentedNames
import PkgWithUndocumentedNamesInSubmodule
import PkgWithoutUndocumentedNames

# Pass
results = @testtestset begin
    Aqua.test_undocumented_names(PkgWithoutUndocumentedNames)
end
if VERSION >= v"1.11"
    @test length(results) == 1
    @test results[1] isa Test.Pass
else
    @test length(results) == 0
end

# Fail
println("### Expected output START ###")
results = @testtestset begin
    Aqua.test_undocumented_names(PkgWithUndocumentedNames)
end
println("### Expected output END ###")
if VERSION >= v"1.11"
    @test length(results) == 1
    @test results[1] isa Test.Fail
else
    @test length(results) == 0
end

println("### Expected output START ###")
results = @testtestset begin
    Aqua.test_undocumented_names(PkgWithUndocumentedNamesInSubmodule)
end
println("### Expected output END ###")
if VERSION >= v"1.11"
    @test length(results) == 1
    @test results[1] isa Test.Fail
else
    @test length(results) == 0
end

# Broken
println("### Expected output START ###")
results = @testtestset begin
    Aqua.test_undocumented_names(PkgWithUndocumentedNames; broken = true)
end
println("### Expected output END ###")
if VERSION >= v"1.11"
    @test length(results) == 1
    @test results[1] isa Test.Broken
else
    @test length(results) == 0
end

end  # module


================================================
FILE: test/test_utils.jl
================================================
module TestUtils

using Aqua: askwargs
using Test

@testset "askwargs" begin
    @test_throws ArgumentError("expect `true`") askwargs(false)
    @test askwargs(true) === NamedTuple()
    @test askwargs(()) === NamedTuple()
    @test askwargs((a = 1,)) === (a = 1,)
end

end  # module
Download .txt
gitextract_3yp6x4rr/

├── .JuliaFormatter.toml
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── DocPreviewCleanup.yml
│       ├── TagBot.yml
│       ├── code-style.yml
│       ├── docs.yml
│       ├── enforce-labels.yml
│       └── test.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── Project.toml
├── README.md
├── docs/
│   ├── Project.toml
│   ├── changelog.jl
│   ├── instantiate.jl
│   ├── make.jl
│   └── src/
│       ├── ambiguities.md
│       ├── deps_compat.md
│       ├── exports.md
│       ├── index.md
│       ├── persistent_tasks.md
│       ├── piracies.md
│       ├── project_extras.md
│       ├── stale_deps.md
│       ├── test_all.md
│       ├── unbound_args.md
│       └── undocumented_names.md
├── src/
│   ├── Aqua.jl
│   ├── ambiguities.jl
│   ├── deps_compat.jl
│   ├── exports.jl
│   ├── persistent_tasks.jl
│   ├── piracies.jl
│   ├── precompile.jl
│   ├── project_extras.jl
│   ├── stale_deps.jl
│   ├── unbound_args.jl
│   ├── undocumented_names.jl
│   └── utils.jl
└── test/
    ├── pkgs/
    │   ├── AquaTesting.jl
    │   ├── PersistentTasks/
    │   │   ├── PersistentTask/
    │   │   │   ├── Project.toml
    │   │   │   └── src/
    │   │   │       └── PersistentTask.jl
    │   │   ├── TransientTask/
    │   │   │   ├── Project.toml
    │   │   │   └── src/
    │   │   │       └── TransientTask.jl
    │   │   └── UsesBoth/
    │   │       ├── Project.toml
    │   │       └── src/
    │   │           └── UsesBoth.jl
    │   ├── PiracyForeignProject/
    │   │   ├── Project.toml
    │   │   └── src/
    │   │       └── PiracyForeignProject.jl
    │   ├── PkgUnboundArgs.jl
    │   ├── PkgWithAmbiguities.jl
    │   ├── PkgWithUndefinedExports.jl
    │   ├── PkgWithUndocumentedNames.jl
    │   ├── PkgWithUndocumentedNamesInSubmodule.jl
    │   ├── PkgWithoutUndocumentedNames.jl
    │   └── sample/
    │       ├── PkgWithCompatibleTestProject/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithCompatibleTestProject.jl
    │       │   └── test/
    │       │       └── Project.toml
    │       ├── PkgWithIncompatibleTestProject/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithIncompatibleTestProject.jl
    │       │   └── test/
    │       │       └── Project.toml
    │       ├── PkgWithPostJulia12Support/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithPostJulia12Support.jl
    │       │   └── test/
    │       │       └── Project.toml
    │       ├── PkgWithoutDeps/
    │       │   ├── Project.toml
    │       │   ├── src/
    │       │   │   └── PkgWithoutDeps.jl
    │       │   └── test/
    │       │       └── .gitkeep
    │       └── PkgWithoutTestProject/
    │           ├── Project.toml
    │           ├── src/
    │           │   └── PkgWithoutTestProject.jl
    │           └── test/
    │               └── .gitkeep
    ├── preamble.jl
    ├── runtests.jl
    ├── test_ambiguities.jl
    ├── test_deps_compat.jl
    ├── test_exclude.jl
    ├── test_persistent_tasks.jl
    ├── test_piracy.jl
    ├── test_project_extras.jl
    ├── test_smoke.jl
    ├── test_stale_deps.jl
    ├── test_unbound_args.jl
    ├── test_undefined_exports.jl
    ├── test_undocumented_names.jl
    └── test_utils.jl
Condensed preview — 85 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (124K chars).
[
  {
    "path": ".JuliaFormatter.toml",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 464,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/DocPreviewCleanup.yml",
    "chars": 940,
    "preview": "name: Doc Preview Cleanup\n\non:\n  pull_request:\n    types: [closed]\n\njobs:\n  doc-preview-cleanup:\n    runs-on: ubuntu-sli"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "chars": 777,
    "preview": "name: TagBot\n\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\n    inputs:\n      lookback:\n        d"
  },
  {
    "path": ".github/workflows/code-style.yml",
    "chars": 144,
    "preview": "name: Code style\n\non:\n  pull_request:\n\njobs:\n  code-style:\n    runs-on: ubuntu-slim\n    steps:\n      - uses: tkf/julia-c"
  },
  {
    "path": ".github/workflows/docs.yml",
    "chars": 1085,
    "preview": "name: Documentation\n\non:\n  push:\n    branches:\n      - master\n      - 'release-*'\n    tags: '*'\n  pull_request:\n  workfl"
  },
  {
    "path": ".github/workflows/enforce-labels.yml",
    "chars": 699,
    "preview": "name: Enforce PR labels\n\npermissions:\n  contents: read\non:\n  pull_request:\n    types: [labeled, unlabeled, opened, reope"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 2188,
    "preview": "name: Run tests\n\non:\n  push:\n    branches:\n      - master\n      - 'release-*'\n    tags: '*'\n  pull_request:\n  workflow_d"
  },
  {
    "path": ".gitignore",
    "chars": 105,
    "preview": "*.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",
    "chars": 13747,
    "preview": "# Release notes\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Ch"
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "Copyright (c) 2019 Takafumi Arakaki\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
  },
  {
    "path": "Makefile",
    "chars": 1125,
    "preview": "JULIA:=julia\n\ndefault: help\n\n.PHONY: default changelog docs docs-instantiate generate_badge generate_favicon help test\n\n"
  },
  {
    "path": "Project.toml",
    "chars": 466,
    "preview": "name = \"Aqua\"\nuuid = \"4c88cf16-eb10-579e-8560-4a9242c79595\"\nauthors = [\"Takafumi Arakaki <aka.tkf@gmail.com> and contrib"
  },
  {
    "path": "README.md",
    "chars": 1988,
    "preview": "# Aqua.jl: *A*uto *QU*ality *A*ssurance for Julia packages\n\n[![Stable](https://img.shields.io/badge/docs-stable-blue.svg"
  },
  {
    "path": "docs/Project.toml",
    "chars": 153,
    "preview": "[deps]\nChangelog = \"5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e\"\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\n\n[compat]\n"
  },
  {
    "path": "docs/changelog.jl",
    "chars": 148,
    "preview": "using Changelog\n\nChangelog.generate(\n    Changelog.CommonMark(),\n    joinpath(@__DIR__, \"..\", \"CHANGELOG.md\");\n    repo "
  },
  {
    "path": "docs/instantiate.jl",
    "chars": 717,
    "preview": "# This script can be used to quickly instantiate the docs/Project.toml environment.\nusing Pkg, TOML\n\npackage_directory ="
  },
  {
    "path": "docs/make.jl",
    "chars": 1062,
    "preview": "using Documenter, Aqua, Changelog\n\n# Generate a Documenter-friendly changelog from CHANGELOG.md\nChangelog.generate(\n    "
  },
  {
    "path": "docs/src/ambiguities.md",
    "chars": 578,
    "preview": "# Ambiguities\n\nMethod ambiguities are cases where multiple methods are applicable to a given set of arguments, without h"
  },
  {
    "path": "docs/src/deps_compat.md",
    "chars": 523,
    "preview": "# Compat entries\n\nIn your `Project.toml` you can (and should) use compat entries to specify\nwith which versions of Julia"
  },
  {
    "path": "docs/src/exports.md",
    "chars": 110,
    "preview": "# Undefined exports\n\n## [Test function](@id test_undefined_exports)\n\n```@docs\nAqua.test_undefined_exports\n```\n"
  },
  {
    "path": "docs/src/index.md",
    "chars": 3147,
    "preview": "# Aqua.jl: *A*uto *QU*ality *A*ssurance for Julia packages\n\nAqua.jl provides functions to run a few automatable checks f"
  },
  {
    "path": "docs/src/persistent_tasks.md",
    "chars": 4153,
    "preview": "# Persistent Tasks\n\n## Motivation\n\nJulia 1.10 and higher wait for all running `Task`s to finish\nbefore writing out the p"
  },
  {
    "path": "docs/src/piracies.md",
    "chars": 2593,
    "preview": "# Type piracy\n\nType piracy is a term used to describe adding methods to a foreign function\nwith only foreign arguments.\n"
  },
  {
    "path": "docs/src/project_extras.md",
    "chars": 654,
    "preview": "# Project.toml extras\n\nThere are two different ways to specify test-only dependencies (see [the Pkg docs](https://julial"
  },
  {
    "path": "docs/src/stale_deps.md",
    "chars": 97,
    "preview": "# 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",
    "chars": 311,
    "preview": "# [Test everything](@id test_all)\n\nThis test runs most of the other tests in this module.\nThe defaults should be fine fo"
  },
  {
    "path": "docs/src/unbound_args.md",
    "chars": 984,
    "preview": "# Unbound Type Parameters\n\nAn unbound type parameter is a type parameter with a `where`,\nthat does not occur in the sign"
  },
  {
    "path": "docs/src/undocumented_names.md",
    "chars": 113,
    "preview": "# Undocumented names\n\n## [Test function](@id test_undocumented_names)\n\n```@docs\nAqua.test_undocumented_names\n```\n"
  },
  {
    "path": "src/Aqua.jl",
    "chars": 3563,
    "preview": "module Aqua\n\nusing Base: Docs, PkgId, UUID\nusing Pkg: Pkg, TOML, PackageSpec\nusing Pkg.Types: VersionSpec, semver_spec\nu"
  },
  {
    "path": "src/ambiguities.jl",
    "chars": 8133,
    "preview": "\"\"\"\n    test_ambiguities(package::Union{Module, PkgId})\n    test_ambiguities(packages::Vector{Union{Module, PkgId}})\n\nTe"
  },
  {
    "path": "src/deps_compat.jl",
    "chars": 4078,
    "preview": "\"\"\"\n    Aqua.test_deps_compat(package)\n\nTest that the `Project.toml` of `package` has a `compat` entry for\neach package "
  },
  {
    "path": "src/exports.jl",
    "chars": 1973,
    "preview": "# avoid Base.isbindingresolved deprecation in https://github.com/JuliaLang/julia/pull/57253\nfunction isbindingresolved(m"
  },
  {
    "path": "src/persistent_tasks.jl",
    "chars": 5579,
    "preview": "\"\"\"\n    Aqua.test_persistent_tasks(package)\n\nTest whether loading `package` creates persistent `Task`s\nwhich may block p"
  },
  {
    "path": "src/piracies.jl",
    "chars": 8719,
    "preview": "module Piracy\n\nusing ..Aqua: is_kwcall\n\nusing Test: is_in_mods\n\n# based on Test/Test.jl#detect_ambiguities\n# https://git"
  },
  {
    "path": "src/precompile.jl",
    "chars": 1767,
    "preview": "using PrecompileTools: @compile_workload\n\n# Create a minimal fake package to test. Needs to be at the top-level because\n"
  },
  {
    "path": "src/project_extras.jl",
    "chars": 3605,
    "preview": "\"\"\"\n    test_project_extras(package::Union{Module, PkgId})\n    test_project_extras(packages::Vector{Union{Module, PkgId}"
  },
  {
    "path": "src/stale_deps.jl",
    "chars": 3151,
    "preview": "\"\"\"\n    Aqua.test_stale_deps(package; ignore::AbstractVector{Symbol} = Symbol[])\n\nTest that `package` loads all dependen"
  },
  {
    "path": "src/unbound_args.jl",
    "chars": 1319,
    "preview": "\"\"\"\n    test_unbound_args(m::Module; broken::Bool = false)\n\nTest that all methods in `m` and its submodules do not have\n"
  },
  {
    "path": "src/undocumented_names.jl",
    "chars": 1685,
    "preview": "\"\"\"\n    test_undocumented_names(m::Module; broken::Bool = false)\n\nTest that all public names in `m` and its recursive su"
  },
  {
    "path": "src/utils.jl",
    "chars": 2247,
    "preview": "askwargs(kwargs) = (; kwargs...)\nfunction askwargs(flag::Bool)\n    if !flag\n        throw(ArgumentError(\"expect `true`\")"
  },
  {
    "path": "test/pkgs/AquaTesting.jl",
    "chars": 1305,
    "preview": "module AquaTesting\n\nusing Base: PkgId, UUID\nusing Pkg\nusing Test\n\n# Taken from Test/test/runtests.jl\nmutable struct NoTh"
  },
  {
    "path": "test/pkgs/PersistentTasks/PersistentTask/Project.toml",
    "chars": 70,
    "preview": "name = \"PersistentTask\"\nuuid = \"e5c298b6-d81d-47aa-a9ed-5ea983e22986\"\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/PersistentTask/src/PersistentTask.jl",
    "chars": 156,
    "preview": "module PersistentTask\n\nconst t = Ref{Any}()\n__init__() = t[] = Timer(0.1; interval = 1)   # create a persistent `Timer` "
  },
  {
    "path": "test/pkgs/PersistentTasks/TransientTask/Project.toml",
    "chars": 69,
    "preview": "name = \"TransientTask\"\nuuid = \"94ae9332-58b0-4342-989c-0a7e44abcca9\"\n"
  },
  {
    "path": "test/pkgs/PersistentTasks/TransientTask/src/TransientTask.jl",
    "chars": 112,
    "preview": "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",
    "chars": 183,
    "preview": "name = \"UsesBoth\"\nuuid = \"96f12b6e-60f8-43dc-b131-049a88a2f499\"\n\n[deps]\nPersistentTask = \"e5c298b6-d81d-47aa-a9ed-5ea983"
  },
  {
    "path": "test/pkgs/PersistentTasks/UsesBoth/src/UsesBoth.jl",
    "chars": 81,
    "preview": "module UsesBoth\n\nusing TransientTask\nusing PersistentTask\n\nend # module UsesBoth\n"
  },
  {
    "path": "test/pkgs/PiracyForeignProject/Project.toml",
    "chars": 76,
    "preview": "name = \"PiracyForeignProject\"\nuuid = \"f592ac8b-a2e8-4dd0-be7a-e4053dab5b76\"\n"
  },
  {
    "path": "test/pkgs/PiracyForeignProject/src/PiracyForeignProject.jl",
    "chars": 143,
    "preview": "module PiracyForeignProject\n\nstruct ForeignType end\nstruct ForeignParameterizedType{T} end\n\nstruct ForeignNonSingletonTy"
  },
  {
    "path": "test/pkgs/PkgUnboundArgs.jl",
    "chars": 321,
    "preview": "module PkgUnboundArgs\n\n# Putting it in a submodule to test that `recursive=true` is used.\nmodule M25341\n_totuple(::Type{"
  },
  {
    "path": "test/pkgs/PkgWithAmbiguities.jl",
    "chars": 1169,
    "preview": "module PkgWithAmbiguities\n\n# 1 ambiguity\nf(::Any, ::Int) = 1\nf(::Int, ::Any) = 2\nconst num_ambs_f = 1\n\n# 2 ambiguities:\n"
  },
  {
    "path": "test/pkgs/PkgWithUndefinedExports.jl",
    "chars": 69,
    "preview": "module PkgWithUndefinedExports\n\nexport undefined_name\n\nend  # module\n"
  },
  {
    "path": "test/pkgs/PkgWithUndocumentedNames.jl",
    "chars": 333,
    "preview": "module PkgWithUndocumentedNames\n\n\"\"\"\n    documented_function\n\"\"\"\nfunction documented_function end\n\nfunction undocumented"
  },
  {
    "path": "test/pkgs/PkgWithUndocumentedNamesInSubmodule.jl",
    "chars": 170,
    "preview": "module PkgWithUndocumentedNamesInSubmodule\n\n\"\"\"\n    DocumentedStruct\n\"\"\"\nstruct DocumentedStruct end\n\nmodule SubModule\n\n"
  },
  {
    "path": "test/pkgs/PkgWithoutUndocumentedNames.jl",
    "chars": 260,
    "preview": "\"\"\"\n    PkgWithoutUndocumentedNames\n\"\"\"\nmodule PkgWithoutUndocumentedNames\n\n\"\"\"\n    documented_function\n\"\"\"\nfunction doc"
  },
  {
    "path": "test/pkgs/sample/PkgWithCompatibleTestProject/Project.toml",
    "chars": 241,
    "preview": "name = \"PkgWithCompatibleTestProject\"\nuuid = \"6e4a843a-fdff-4fa3-bb5a-e4ae67826963\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nPkg "
  },
  {
    "path": "test/pkgs/sample/PkgWithCompatibleTestProject/src/PkgWithCompatibleTestProject.jl",
    "chars": 40,
    "preview": "module PkgWithCompatibleTestProject end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithCompatibleTestProject/test/Project.toml",
    "chars": 98,
    "preview": "[deps]\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithIncompatibleTestProject/Project.toml",
    "chars": 245,
    "preview": "name = \"PkgWithIncompatibleTestProject\"\nuuid = \"1649c42c-2196-4c52-9963-79822cd6227b\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nRE"
  },
  {
    "path": "test/pkgs/sample/PkgWithIncompatibleTestProject/src/PkgWithIncompatibleTestProject.jl",
    "chars": 42,
    "preview": "module PkgWithIncompatibleTestProject end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithIncompatibleTestProject/test/Project.toml",
    "chars": 101,
    "preview": "[deps]\nRandom = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithPostJulia12Support/Project.toml",
    "chars": 240,
    "preview": "name = \"PkgWithPostJulia12Support\"\nuuid = \"7231ce0e-e308-4079-b49f-19e33cc3ac6e\"\n\n[compat]\njulia = \"1.2\"\n\n[extras]\nPkg ="
  },
  {
    "path": "test/pkgs/sample/PkgWithPostJulia12Support/src/PkgWithPostJulia12Support.jl",
    "chars": 37,
    "preview": "module PkgWithPostJulia12Support end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithPostJulia12Support/test/Project.toml",
    "chars": 99,
    "preview": "[deps]\nREPL = \"3fa0cd96-eef1-5676-8a61-b3b8758bbffb\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutDeps/Project.toml",
    "chars": 175,
    "preview": "name = \"PkgWithoutDeps\"\nuuid = \"3922d3f4-c8f6-c8a8-00da-60b44ed8eac6\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nTest = \"8dfed614-e"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutDeps/src/PkgWithoutDeps.jl",
    "chars": 26,
    "preview": "module PkgWithoutDeps end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutDeps/test/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/pkgs/sample/PkgWithoutTestProject/Project.toml",
    "chars": 182,
    "preview": "name = \"PkgWithoutTestProject\"\nuuid = \"8981f3dd-97fd-4684-8ec7-7b0c42f64e2e\"\n\n[compat]\njulia = \"1\"\n\n[extras]\nTest = \"8df"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutTestProject/src/PkgWithoutTestProject.jl",
    "chars": 33,
    "preview": "module PkgWithoutTestProject end\n"
  },
  {
    "path": "test/pkgs/sample/PkgWithoutTestProject/test/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/preamble.jl",
    "chars": 196,
    "preview": "let path = joinpath(@__DIR__, \"pkgs\")\n    if path ∉ LOAD_PATH\n        pushfirst!(LOAD_PATH, path)\n    end\nend\n\nusing Tes"
  },
  {
    "path": "test/runtests.jl",
    "chars": 189,
    "preview": "module TestAqua\n\nusing Test\n\n@testset \"$file\" for file in sort([\n    file for file in readdir(@__DIR__) if match(r\"^test"
  },
  {
    "path": "test/test_ambiguities.jl",
    "chars": 2476,
    "preview": "module TestAmbiguities\n\ninclude(\"preamble.jl\")\n\n@testset begin\n    using PkgWithAmbiguities\n\n    using PkgWithAmbiguitie"
  },
  {
    "path": "test/test_deps_compat.jl",
    "chars": 3582,
    "preview": "module TestDepsCompat\n\ninclude(\"preamble.jl\")\nusing Aqua: find_missing_deps_compat, has_julia_compat\n\nconst DictSA = Dic"
  },
  {
    "path": "test/test_exclude.jl",
    "chars": 1436,
    "preview": "module TestExclude\n\ninclude(\"preamble.jl\")\nusing Base: PkgId\nusing Aqua: getexclude, normalize_exclude, normalize_exclud"
  },
  {
    "path": "test/test_persistent_tasks.jl",
    "chars": 1272,
    "preview": "module TestPersistentTasks\n\ninclude(\"preamble.jl\")\nusing Base: PkgId, UUID\nusing Pkg: TOML\n\nfunction getid(name)\n    pat"
  },
  {
    "path": "test/test_piracy.jl",
    "chars": 4781,
    "preview": "push!(LOAD_PATH, joinpath(@__DIR__, \"pkgs\", \"PiracyForeignProject\"))\n\nbaremodule PiracyModule\n\nusing PiracyForeignProjec"
  },
  {
    "path": "test/test_project_extras.jl",
    "chars": 2082,
    "preview": "module TestProjectExtras\n\ninclude(\"preamble.jl\")\nusing Aqua: is_julia12_or_later\nusing Base: PkgId, UUID\n\n@testset \"is_j"
  },
  {
    "path": "test/test_smoke.jl",
    "chars": 343,
    "preview": "module TestSmoke\n\nusing Aqua\n\n# test defaults\nAqua.test_all(Aqua)\n\n# test everything else\nAqua.test_all(\n    Aqua;\n    a"
  },
  {
    "path": "test/test_stale_deps.jl",
    "chars": 2693,
    "preview": "module TestStaleDeps\n\ninclude(\"preamble.jl\")\nusing Base: PkgId, UUID\nusing Aqua: find_stale_deps_2\n\n@testset \"find_stale"
  },
  {
    "path": "test/test_unbound_args.jl",
    "chars": 470,
    "preview": "module TestUnboundArgs\n\ninclude(\"preamble.jl\")\n\nusing PkgUnboundArgs\n\n@testset begin\n    println(\"### Expected output ST"
  },
  {
    "path": "test/test_undefined_exports.jl",
    "chars": 633,
    "preview": "module TestUndefinedExports\n\ninclude(\"preamble.jl\")\n\nusing PkgWithUndefinedExports\n\n@testset begin\n    @test Aqua.undefi"
  },
  {
    "path": "test/test_undocumented_names.jl",
    "chars": 1357,
    "preview": "module TestUndocumentedNames\n\ninclude(\"preamble.jl\")\n\nimport PkgWithUndocumentedNames\nimport PkgWithUndocumentedNamesInS"
  },
  {
    "path": "test/test_utils.jl",
    "chars": 284,
    "preview": "module TestUtils\n\nusing Aqua: askwargs\nusing Test\n\n@testset \"askwargs\" begin\n    @test_throws ArgumentError(\"expect `tru"
  }
]

About this extraction

This page contains the full source code of the JuliaTesting/Aqua.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 85 files (111.2 KB), approximately 34.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!