├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.yaml
│ ├── build.yaml
│ ├── chore.yaml
│ ├── ci.yaml
│ ├── config.yml
│ ├── documentation.yaml
│ ├── feature_request.yaml
│ ├── performance.yaml
│ ├── refactor.yaml
│ ├── revert.yaml
│ ├── style.yaml
│ └── test.yaml
├── PULL_REQUEST_TEMPLATE.md
├── cspell.json
├── dependabot.yaml
└── workflows
│ ├── pana_score.yaml
│ ├── pub_publish.yaml
│ ├── semantic_pull_request.yaml
│ ├── spell_check.yaml
│ ├── sync_labels.yaml
│ └── very_good_test_runner.yaml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── build.yaml
├── coverage_badge.svg
├── example
└── main.dart
├── lib
├── src
│ ├── models
│ │ ├── models.dart
│ │ ├── test_event.dart
│ │ └── test_event.g.dart
│ └── very_good_test_runner.dart
└── very_good_test_runner.dart
├── pubspec.yaml
├── test
└── src
│ ├── models
│ └── test_event_test.dart
│ └── very_good_test_runner_test.dart
└── tool
└── release_ready.sh
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Every request must be reviewed and accepted by:
2 |
3 | * @VeryGoodOpenSource/codeowners
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Create a report to help us improve
3 | title: "fix: "
4 | labels: [bug]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: A clear and concise description of what the bug is.
11 | placeholder: "Describe the bug."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: setps-to-reproduce
16 | attributes:
17 | label: Steps To Reproduce
18 | description: A set of instructions, step by step, explaining how to reproduce the bug.
19 | placeholder: |
20 | 1. Go to '...'
21 | 2. Click on '....'
22 | 3. Scroll down to '....'
23 | 4. See error
24 | validations:
25 | required: true
26 | - type: textarea
27 | id: expected-behavior
28 | attributes:
29 | label: Expected Behavior
30 | description: A clear and concise description of what you expected to happen.
31 | placeholder: "Describe what you expected to happen."
32 | validations:
33 | required: true
34 | - type: textarea
35 | id: additional-context
36 | attributes:
37 | label: Additional Context
38 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
39 | placeholder: "Provide context here."
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build System
2 | description: Changes that affect the build system or external dependencies
3 | title: "build: "
4 | labels: [build]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Describe what changes need to be done to the build system and why
11 | placeholder: "Describe the build system change."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/chore.yaml:
--------------------------------------------------------------------------------
1 | name: Chore
2 | description: Other changes that don't modify source or test files
3 | title: "chore: "
4 | labels: [chore]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Clearly describe what change is needed and why. If this changes code then please use another issue type.
11 | placeholder: "Provide a description of the chore."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] No functional changes to the code.
21 | - [ ] All CI/CD checks are passing.
22 | - [ ] There is no drop in the test coverage percentage.
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: additional-context
27 | attributes:
28 | label: Additional Context
29 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
30 | placeholder: "Provide context here."
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/ci.yaml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 | description: Changes to the CI configuration files and scripts
3 | title: "ci: "
4 | labels: [ci]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Describe what changes need to be done to the CI/CD system and why.
11 | placeholder: "Provide a description of the changes that need to be done to the CI/CD system."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.yaml:
--------------------------------------------------------------------------------
1 | name: Documentation
2 | description: Improve the documentation so all collaborators have a common understanding
3 | title: "docs: "
4 | labels: [documentation]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Clearly describe what documentation you are looking to add or improve.
11 | placeholder: "Provide a description of the documentation changes."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] No functional changes to the code.
21 | - [ ] All CI/CD checks are passing.
22 | - [ ] There is no drop in the test coverage percentage.
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: additional-context
27 | attributes:
28 | label: Additional Context
29 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
30 | placeholder: "Provide context here."
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: A new feature to be added to the project
3 | title: "feat: "
4 | labels: [feature]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Clearly describe what you are looking to add. The more business/user context the better.
11 | placeholder: "Provide a description of the feature."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/performance.yaml:
--------------------------------------------------------------------------------
1 | name: Performance Update
2 | description: A code change that improves performance
3 | title: "perf: "
4 | labels: [performance]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Clearly describe what code needs to be changed and what the performance impact is going to be. Bonus point's if you can tie this directly to user experience.
11 | placeholder: " Provide a description of the performance update."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/refactor.yaml:
--------------------------------------------------------------------------------
1 | name: Refactor
2 | description: A code change that neither fixes a bug nor adds a feature
3 | title: "refactor: "
4 | labels: [refactor]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Clearly describe what needs to be refactored and why. Please provide links to related issues (bugs or upcoming features) in order to help prioritize.
11 | placeholder: "Provide a description of the refactor."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/revert.yaml:
--------------------------------------------------------------------------------
1 | name: Revert
2 | description: Revert a previous commit
3 | title: "revert: "
4 | labels: [revert]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Provide a link to a PR/Commit that you are looking to revert and why.
11 | placeholder: "Provide a description of and link to the commit that needs to be reverted."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] Change has been reverted.
21 | - [ ] No change in unit/widget test coverage has happened.
22 | - [ ] A new ticket is created for any follow on work that needs to happen.
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: additional-context
27 | attributes:
28 | label: Additional Context
29 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
30 | placeholder: "Provide context here."
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/style.yaml:
--------------------------------------------------------------------------------
1 | name: Style
2 | description: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
3 | title: "style: "
4 | labels: [style]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: Clearly describe what you are looking to change and why.
11 | placeholder: "Provide a description of the style changes."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the unit or widget test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test
2 | description: Adding missing tests or correcting existing tests
3 | title: "test: "
4 | labels: [test]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Description
10 | description: List out the tests that need to be added or changed. Please also include any information as to why this was not covered in the past.
11 | placeholder: "Provide a description of the tests that need to be added or changed."
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: requirements
16 | attributes:
17 | label: Requirements
18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible.
19 | value: |
20 | - [ ] All CI/CD checks are passing.
21 | - [ ] There is no drop in the test coverage percentage.
22 | validations:
23 | required: true
24 | - type: textarea
25 | id: additional-context
26 | attributes:
27 | label: Additional Context
28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here.
29 | placeholder: "Provide context here."
30 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | ## Status
10 |
11 | **READY/IN DEVELOPMENT/HOLD**
12 |
13 | ## Description
14 |
15 |
16 |
17 | ## Type of Change
18 |
19 |
20 |
21 | - [ ] ✨ New feature (non-breaking change which adds functionality)
22 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
23 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
24 | - [ ] 🧹 Code refactor
25 | - [ ] ✅ Build configuration change
26 | - [ ] 📝 Documentation
27 | - [ ] 🗑️ Chore
28 |
--------------------------------------------------------------------------------
/.github/cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2",
3 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
4 | "dictionaries": ["vgv_allowed", "vgv_forbidden"],
5 | "dictionaryDefinitions": [
6 | {
7 | "name": "vgv_allowed",
8 | "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/allowed.txt",
9 | "description": "Allowed VGV Spellings"
10 | },
11 | {
12 | "name": "vgv_forbidden",
13 | "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/forbidden.txt",
14 | "description": "Forbidden VGV Spellings"
15 | }
16 | ],
17 | "useGitignore": true
18 | }
19 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | - package-ecosystem: "pub"
8 | directory: "/"
9 | schedule:
10 | interval: "daily"
11 |
--------------------------------------------------------------------------------
/.github/workflows/pana_score.yaml:
--------------------------------------------------------------------------------
1 | name: pana_score
2 |
3 | concurrency:
4 | group: ${{ github.workflow }}-${{ github.ref }}
5 | cancel-in-progress: true
6 |
7 | on:
8 | push:
9 | branches:
10 | - main
11 | pull_request:
12 | branches:
13 | - main
14 |
15 | jobs:
16 | pana_score:
17 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/pana.yml@v1
18 |
--------------------------------------------------------------------------------
/.github/workflows/pub_publish.yaml:
--------------------------------------------------------------------------------
1 | name: pub_publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v[0-9]+.[0-9]+.[0-9]+*"
7 |
8 | jobs:
9 | publish:
10 | permissions:
11 | id-token: write # Required for authentication using OIDC
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: 📚 Git Checkout
15 | uses: actions/checkout@v4
16 | - name: 🎯 Setup Dart
17 | uses: dart-lang/setup-dart@v1
18 | - name: 🐦 Setup Flutter
19 | uses: subosito/flutter-action@v2
20 | - name: 📦 Install Dependencies
21 | run: flutter pub get
22 | - name: 🌵 Dry Run
23 | run: dart pub publish --dry-run
24 | - name: 📢 Publish
25 | run: dart pub publish --force
26 |
--------------------------------------------------------------------------------
/.github/workflows/semantic_pull_request.yaml:
--------------------------------------------------------------------------------
1 | name: semantic_pull_request
2 |
3 | concurrency:
4 | group: ${{ github.workflow }}-${{ github.ref }}
5 | cancel-in-progress: true
6 |
7 | on:
8 | push:
9 | branches:
10 | - main
11 | pull_request:
12 | branches:
13 | - main
14 |
15 | jobs:
16 | semantic-pull-request:
17 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1
18 |
--------------------------------------------------------------------------------
/.github/workflows/spell_check.yaml:
--------------------------------------------------------------------------------
1 | name: spell_check
2 |
3 | concurrency:
4 | group: ${{ github.workflow }}-${{ github.ref }}
5 | cancel-in-progress: true
6 |
7 | on:
8 | push:
9 | branches:
10 | - main
11 | pull_request:
12 | branches:
13 | - main
14 |
15 | jobs:
16 | spell-check:
17 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1
18 | with:
19 | includes: "**/*.md"
20 | modified_files_only: false
21 |
--------------------------------------------------------------------------------
/.github/workflows/sync_labels.yaml:
--------------------------------------------------------------------------------
1 | name: ♻️ Sync Labels
2 |
3 | on:
4 | push:
5 | paths:
6 | - .github/labels.yml
7 | branches:
8 | - main
9 | workflow_dispatch:
10 |
11 | jobs:
12 | labels:
13 | name: ♻️ Sync labels
14 | runs-on: ubuntu-20.04
15 | steps:
16 | - name: ⤵️ Check out code from GitHub
17 | uses: actions/checkout@v4
18 |
19 | - name: 🚀 Run Label Sync
20 | uses: srealmoreno/label-sync-action@v2
21 | with:
22 | config-file: https://raw.githubusercontent.com/VeryGoodOpenSource/.github/main/.github/labels.yml
23 |
--------------------------------------------------------------------------------
/.github/workflows/very_good_test_runner.yaml:
--------------------------------------------------------------------------------
1 | name: very_good_test_runner
2 | concurrency:
3 | group: ${{ github.workflow }}-${{ github.ref }}
4 | cancel-in-progress: true
5 |
6 | on:
7 | push:
8 | branches:
9 | - main
10 | pull_request:
11 | branches:
12 | - main
13 |
14 | jobs:
15 | build:
16 | strategy:
17 | matrix:
18 | flutter_version:
19 | - "3.24.0" # The Flutter version with a Dart SDK that matches the minimum Dart SDK version supported by the package.
20 | - "3.x" # The Flutter version with a Dart SDK that matches the maximum Dart SDK version supported by the package.
21 |
22 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
23 | with:
24 | flutter_version: ${{ matrix.flutter_version }}
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | build/
7 | pubspec.lock
8 |
9 | # Android Studio and IntelliJ
10 | .idea
11 |
12 | # Files related to tests
13 | coverage/
14 | .test_coverage.dart
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.3.0
2 |
3 | - chore: tighten dependencies ([#29](https://github.com/VeryGoodOpenSource/very_good_test_runner/pull/29))
4 |
5 | # 0.2.0
6 |
7 | - ci: use semantic-pull-request workflow ([#8](https://github.com/VeryGoodOpenSource/very_good_test_runner/pull/8))
8 | - ci: add dependabot ([#9](https://github.com/VeryGoodOpenSource/very_good_test_runner/pull/9))
9 | - feat!: update workflows, add spellcheck, update very_good_analysis ([#16](https://github.com/VeryGoodOpenSource/very_good_test_runner/pull/16))
10 | - feat!: Update Dart to 3.0.0 ([#20](https://github.com/VeryGoodOpenSource/very_good_test_runner/pull/20))
11 |
12 | # [0.1.2](https://github.com/VeryGoodOpenSource/very_good_test_runner/compare/v0.1.1...v0.1.2) (2022-05-05)
13 |
14 | ### Features
15 |
16 | - add ExitTestEvent to indicate the process has exited ([#6](https://github.com/VeryGoodOpenSource/very_good_test_runner/issues/6)) ([8f37fbd](https://github.com/VeryGoodOpenSource/very_good_test_runner/commit/8f37fbde9dafaef5702b75c8aad06e4fdca0d015))
17 |
18 | ## [0.1.1](https://github.com/VeryGoodOpenSource/very_good_test_runner/compare/v0.1.0...v0.1.1) (2022-03-09)
19 |
20 | ### Bug Fixes
21 |
22 | - README logo order ([aaba957](https://github.com/VeryGoodOpenSource/very_good_test_runner/commit/aaba957d3bc6739e7d7314cdb8a0dd1fae5e696d))
23 |
24 | # [0.1.0](https://github.com/VeryGoodOpenSource/very_good_test_runner/compare/3001cef12ee5fb5c52a1652ff24209037a225ece...v0.1.0) (2022-03-09)
25 |
26 | ### Features
27 |
28 | - add dartTest ([#3](https://github.com/VeryGoodOpenSource/very_good_test_runner/issues/3)) ([10336d2](https://github.com/VeryGoodOpenSource/very_good_test_runner/commit/10336d2bce2fbb0e19d85d636737dcc7b92b8e77))
29 | - add flutterTest ([#2](https://github.com/VeryGoodOpenSource/very_good_test_runner/issues/2)) ([4db0c6b](https://github.com/VeryGoodOpenSource/very_good_test_runner/commit/4db0c6b0da2d99acad0582a33b8e5885bdc6bf36))
30 | - export TestEvents ([#1](https://github.com/VeryGoodOpenSource/very_good_test_runner/issues/1)) ([1de845a](https://github.com/VeryGoodOpenSource/very_good_test_runner/commit/1de845a0f6ce3fcbdfc77d10f76fe38f4c77a858))
31 | - generate package via very_good_cli 🦄 ([3001cef](https://github.com/VeryGoodOpenSource/very_good_test_runner/commit/3001cef12ee5fb5c52a1652ff24209037a225ece))
32 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at hello@verygood.ventures. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faqs
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Very Good Test Runner
2 |
3 | First off, thanks for taking the time to contribute! 🎉👍
4 |
5 | This project is opinionated and follows patterns and practices used by the team at [Very Good Ventures][very_good_ventures_link]. **At this time, we welcome bug tickets but will not be accepting feature requests because the roadmap and scope of this project is still being defined.**
6 |
7 | ## Creating a Bug Report
8 |
9 | We highly recommend [creating an issue][bug_report_link] if you have found a bug rather than immediately opening a pull request. This lets us reach an agreement on a fix before you put significant effort into a pull request. Please use the built-in [Bug Report][bug_report_link] template and provide as much information as possible including detailed reproduction steps. Once one of the package maintainers has reviewed the issue and an agreement is reached regarding the fix, a pull request can be created.
10 |
11 | ## Creating a Pull Request
12 |
13 | Before creating a pull request please:
14 |
15 | 1. Fork the repository and create your branch from `main`.
16 | 1. Install all dependencies (`flutter packages get` or `pub get`).
17 | 1. Squash your commits and ensure you have a meaningful, [semantic][conventional_commits_link] commit message.
18 | 1. Add tests! Pull Requests without 100% test coverage will not be approved.
19 | 1. Ensure the existing test suite passes locally.
20 | 1. Format your code (`dart format .`).
21 | 1. Analyze your code (`dart analyze --fatal-infos --fatal-warnings .`).
22 | 1. Create the Pull Request.
23 | 1. Verify that all status checks are passing.
24 |
25 | While the prerequisites above must be satisfied prior to having your
26 | pull request reviewed, the reviewer(s) may ask you to complete additional
27 | work, tests, or other changes before your pull request can be ultimately
28 | accepted.
29 |
30 | [conventional_commits_link]: https://www.conventionalcommits.org/en/v1.0.0
31 | [bug_report_link]: https://github.com/VeryGoodOpenSource/very_good_test_runner/issues/new?assignees=&labels=bug&template=bug_report.md&title=fix%3A+
32 | [very_good_ventures_link]: https://verygood.ventures
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Very Good Ventures
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Very Good Test Runner
2 |
3 | [![Very Good Ventures][logo_black]][very_good_ventures_link_light]
4 | [![Very Good Ventures][logo_white]][very_good_ventures_link_dark]
5 |
6 | Developed with 💙 by [Very Good Ventures][very_good_ventures_link] 🦄
7 |
8 | [![ci][ci_badge]][ci_link]
9 | [![coverage][coverage_badge]][ci_link]
10 | [![pub package][pub_badge]][pub_link]
11 | [![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
12 | [![License: MIT][license_badge]][license_link]
13 |
14 | This package is a test runner for Flutter and Dart created by Very Good Ventures. It is intended to be used when writing custom tooling that runs Flutter or Dart tests and exposes a stream of `TestEvent` instances. For more information about the various `TestEvent` types, refer to the [JSON Reporter Test Protocol][json_reporter_test_protocol_link].
15 |
16 | ## Usage
17 |
18 | ```dart
19 | import 'package:very_good_test_runner/very_good_test_runner.dart';
20 |
21 | void main() {
22 | const arguments = ['--coverage'];
23 | const workingDirectory = 'path/to/project';
24 |
25 | // Run `dart test` process.
26 | dartTest(
27 | arguments: arguments,
28 | workingDirectory: workingDirectory,
29 | ).listen((TestEvent event) {
30 | // React to `TestEvent` instances.
31 | print(event);
32 | });
33 |
34 | // Run `flutter test` process.
35 | flutterTest(
36 | arguments: arguments,
37 | workingDirectory: workingDirectory,
38 | ).listen((TestEvent event) {
39 | // React to `TestEvent` instances.
40 | print(event);
41 | });
42 | }
43 | ```
44 |
45 | [ci_badge]: https://github.com/VeryGoodOpenSource/very_good_test_runner/workflows/very_good_test_runner/badge.svg
46 | [ci_link]: https://github.com/VeryGoodOpenSource/very_good_test_runner/actions
47 | [coverage_badge]: https://raw.githubusercontent.com/VeryGoodOpenSource/very_good_test_runner/main/coverage_badge.svg
48 | [json_reporter_test_protocol_link]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md
49 | [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
50 | [license_link]: https://opensource.org/licenses/MIT
51 | [logo_black]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_black.png#gh-light-mode-only
52 | [logo_white]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_white.png#gh-dark-mode-only
53 | [pub_badge]: https://img.shields.io/pub/v/very_good_test_runner.svg
54 | [pub_link]: https://pub.dartlang.org/packages/very_good_test_runner
55 | [very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
56 | [very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
57 | [very_good_ventures_link]: https://verygood.ventures/?utm_source=github
58 | [very_good_ventures_link_dark]: https://verygood.ventures/?utm_source=github#gh-dark-mode-only
59 | [very_good_ventures_link_light]: https://verygood.ventures/?utm_source=github#gh-light-mode-only
60 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.7.0.0.yaml
2 |
--------------------------------------------------------------------------------
/build.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | $default:
3 | builders:
4 | source_gen|combining_builder:
5 | options:
6 | ignore_for_file:
7 | - document_ignores
8 | - implicit_dynamic_parameter
9 | - cast_nullable_to_non_nullable
10 | - require_trailing_commas
11 | - lines_longer_than_80_chars
12 | json_serializable:
13 | options:
14 | create_to_json: false
15 | checked: true
16 |
--------------------------------------------------------------------------------
/coverage_badge.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/example/main.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_print
2 |
3 | import 'package:very_good_test_runner/very_good_test_runner.dart';
4 |
5 | void main() {
6 | // React to `TestEvent` instances.
7 | flutterTest().listen(print);
8 | }
9 |
--------------------------------------------------------------------------------
/lib/src/models/models.dart:
--------------------------------------------------------------------------------
1 | export 'test_event.dart';
2 |
--------------------------------------------------------------------------------
/lib/src/models/test_event.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'test_event.g.dart';
4 |
5 | /// {@template test_event}
6 | /// This is the root class of the protocol.
7 | /// All root-level objects emitted by the JSON reporter
8 | /// will be subclasses of [TestEvent].
9 | /// https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md
10 | /// {@endtemplate}
11 | abstract class TestEvent {
12 | /// {@macro test_event}
13 | const TestEvent({required this.type, required this.time});
14 |
15 | /// Converts [json] into a [TestEvent].
16 | static TestEvent fromJson(Map json) {
17 | final type = json['type'] as String?;
18 | switch (type) {
19 | case 'start':
20 | return StartTestEvent.fromJson(json);
21 | case 'allSuites':
22 | return AllSuitesTestEvent.fromJson(json);
23 | case 'suite':
24 | return SuiteTestEvent.fromJson(json);
25 | case 'debug':
26 | return DebugTestEvent.fromJson(json);
27 | case 'group':
28 | return GroupTestEvent.fromJson(json);
29 | case 'testStart':
30 | return TestStartEvent.fromJson(json);
31 | case 'print':
32 | return MessageTestEvent.fromJson(json);
33 | case 'error':
34 | return ErrorTestEvent.fromJson(json);
35 | case 'testDone':
36 | return TestDoneEvent.fromJson(json);
37 | case 'done':
38 | return DoneTestEvent.fromJson(json);
39 | case 'exit':
40 | return ExitTestEvent.fromJson(json);
41 | default:
42 | throw UnsupportedError('Unsupported type: $type');
43 | }
44 | }
45 |
46 | /// The type of the event.
47 | ///
48 | /// This is always one of the subclass types listed below.
49 | final String type;
50 |
51 | /// The time (in milliseconds) that has elapsed since the test runner started.
52 | final int time;
53 | }
54 |
55 | /// {@template start_test_event}
56 | /// A single start event is emitted before any other events.
57 | /// It indicates that the test runner has started running.
58 | /// {@endtemplate}
59 | @JsonSerializable()
60 | class StartTestEvent extends TestEvent {
61 | /// {@macro start_test_event}
62 | const StartTestEvent({
63 | required this.protocolVersion,
64 | required this.runnerVersion,
65 | required this.pid,
66 | required super.time,
67 | }) : super(type: 'start');
68 |
69 | /// {@macro start_test_event}
70 | factory StartTestEvent.fromJson(Map json) =>
71 | _$StartTestEventFromJson(json);
72 |
73 | /// The version of the JSON reporter protocol being used.
74 | ///
75 | /// This is a semantic version, but it reflects only the version of the
76 | /// protocol—it's not identical to the version of the test runner itself.
77 | final String protocolVersion;
78 |
79 | /// The version of the test runner being used.
80 | ///
81 | /// This is null if for some reason the version couldn't be loaded.
82 | final String? runnerVersion;
83 |
84 | /// The pid of the VM process running the tests.
85 | final int pid;
86 | }
87 |
88 | /// {@template all_suites_test_event}
89 | /// A single suite count event is emitted once the test runner knows the total
90 | /// number of suites that will be loaded over the course of the test run.
91 | /// Because this is determined asynchronously, its position relative to other
92 | /// events (except [StartTestEvent]) is not guaranteed.
93 | /// {@endtemplate}
94 | @JsonSerializable()
95 | class AllSuitesTestEvent extends TestEvent {
96 | /// {@macro all_suites_test_event}
97 | const AllSuitesTestEvent({
98 | required this.count,
99 | required super.time,
100 | }) : super(type: 'allSuites');
101 |
102 | /// {@macro all_suites_test_event}
103 | factory AllSuitesTestEvent.fromJson(Map json) =>
104 | _$AllSuitesTestEventFromJson(json);
105 |
106 | /// The total number of suites that will be loaded.
107 | final int count;
108 | }
109 |
110 | /// {@template suite_test_event}
111 | /// A suite event is emitted before any GroupEvents for groups
112 | /// in a given test suite.
113 | /// This is the only event that contains the full metadata about a suite;
114 | /// future events will refer to the suite by its opaque ID.
115 | /// {@endtemplate}
116 | @JsonSerializable()
117 | class SuiteTestEvent extends TestEvent {
118 | /// {@macro suite_test_event}
119 | const SuiteTestEvent({
120 | required this.suite,
121 | required super.time,
122 | }) : super(type: 'suite');
123 |
124 | /// {@macro suite_test_event}
125 | factory SuiteTestEvent.fromJson(Map json) =>
126 | _$SuiteTestEventFromJson(json);
127 |
128 | /// Metadata about the suite.
129 | final TestSuite suite;
130 | }
131 |
132 | /// {@template debug_test_event}
133 | /// A debug event is emitted after (although not necessarily directly after)
134 | /// a [SuiteTestEvent], and includes information about how to debug that suite.
135 | /// It's only emitted if the --debug flag is passed to the test runner.
136 | /// {@endtemplate}
137 | @JsonSerializable()
138 | class DebugTestEvent extends TestEvent {
139 | /// {@macro debug_test_event}
140 | const DebugTestEvent({
141 | required this.suiteID,
142 | required this.observatory,
143 | required this.remoteDebugger,
144 | required super.time,
145 | }) : super(type: 'debug');
146 |
147 | /// {@macro debug_test_event}
148 | factory DebugTestEvent.fromJson(Map json) =>
149 | _$DebugTestEventFromJson(json);
150 |
151 | /// The suite for which debug information is reported.
152 | final int suiteID;
153 |
154 | /// The HTTP URL for the Dart Observatory, or `null` if the Observatory isn't
155 | /// available for this suite.
156 | final String? observatory;
157 |
158 | /// The HTTP URL for the remote debugger for this suite's host page, or `null`
159 | /// if no remote debugger is available for this suite.
160 | final String? remoteDebugger;
161 | }
162 |
163 | /// {@template group_test_event}
164 | /// A group event is emitted before any
165 | /// [TestStartEvent] for tests in a given group.
166 | /// This is the only event that contains the full metadata about a group;
167 | /// future events will refer to the group by its opaque ID.
168 | /// {@endtemplate}
169 | @JsonSerializable()
170 | class GroupTestEvent extends TestEvent {
171 | /// {@macro group_test_event}
172 | const GroupTestEvent({
173 | required this.group,
174 | required super.time,
175 | }) : super(type: 'group');
176 |
177 | /// {@macro group_test_event}
178 | factory GroupTestEvent.fromJson(Map json) =>
179 | _$GroupTestEventFromJson(json);
180 |
181 | /// Metadata about the group.
182 | final TestGroup group;
183 | }
184 |
185 | /// {@template test_start_event}
186 | /// An event emitted when a test begins running.
187 | /// This is the only event that contains the full metadata about a test;
188 | /// future events will refer to the test by its opaque ID.
189 | /// {@endtemplate}
190 | @JsonSerializable()
191 | class TestStartEvent extends TestEvent {
192 | /// {@macro test_start_event}
193 | const TestStartEvent({
194 | required this.test,
195 | required super.time,
196 | }) : super(type: 'testStart');
197 |
198 | /// {@macro test_start_event}
199 | factory TestStartEvent.fromJson(Map json) =>
200 | _$TestStartEventFromJson(json);
201 |
202 | /// Metadata about the test that started.
203 | final Test test;
204 | }
205 |
206 | /// {@template message_test_event}
207 | /// A MessageEvent indicates that a test emitted a message that
208 | /// should be displayed to the user.
209 | /// The [messageType] field indicates the precise type of this message.
210 | /// Different message types should be visually distinguishable.
211 | /// {@endtemplate}
212 | @JsonSerializable()
213 | class MessageTestEvent extends TestEvent {
214 | /// {@macro message_test_event}
215 | const MessageTestEvent({
216 | required this.testID,
217 | required this.messageType,
218 | required this.message,
219 | required super.time,
220 | }) : super(type: 'print');
221 |
222 | /// {@macro message_test_event}
223 | factory MessageTestEvent.fromJson(Map json) =>
224 | _$MessageTestEventFromJson(json);
225 |
226 | /// The ID of the test that printed a message.
227 | final int testID;
228 |
229 | /// The type of message being printed.
230 | final String messageType;
231 |
232 | /// The message that was printed.
233 | final String message;
234 | }
235 |
236 | /// {@template error_test_event}
237 | /// An [ErrorTestEvent] indicates that a test encountered an uncaught error.
238 | /// Note that this may happen even after the test has completed,
239 | /// in which case it should be considered to have failed.
240 | /// {@endtemplate}
241 | @JsonSerializable()
242 | class ErrorTestEvent extends TestEvent {
243 | /// {@macro error_test_event}
244 | const ErrorTestEvent({
245 | required this.testID,
246 | required this.error,
247 | required this.stackTrace,
248 | required this.isFailure,
249 | required super.time,
250 | }) : super(type: 'error');
251 |
252 | /// {@macro error_test_event}
253 | factory ErrorTestEvent.fromJson(Map json) =>
254 | _$ErrorTestEventFromJson(json);
255 |
256 | /// The ID of the test that experienced the error.
257 | final int testID;
258 |
259 | /// The result of calling toString() on the error object.
260 | final String error;
261 |
262 | /// The error's stack trace, in the stack_trace package format.
263 | final String stackTrace;
264 |
265 | /// Whether the error was a TestFailure.
266 | final bool isFailure;
267 | }
268 |
269 | /// The result of a test.
270 | enum TestResult {
271 | /// the test had no errors
272 | success,
273 |
274 | /// the test had a `TestFailure` but no other errors.
275 | failure,
276 |
277 | /// the test had an error other than `TestFailure`
278 | error
279 | }
280 |
281 | /// {@template test_done_event}
282 | /// An event emitted when a test completes.
283 | /// The result attribute indicates the result of the test.
284 | /// {@endtemplate}
285 | @JsonSerializable()
286 | class TestDoneEvent extends TestEvent {
287 | /// {@macro test_done_event}
288 | const TestDoneEvent({
289 | required this.testID,
290 | required this.result,
291 | required this.hidden,
292 | required this.skipped,
293 | required super.time,
294 | }) : super(type: 'testDone');
295 |
296 | /// {@macro test_done_event}
297 | factory TestDoneEvent.fromJson(Map json) =>
298 | _$TestDoneEventFromJson(json);
299 |
300 | /// The ID of the test that completed.
301 | final int testID;
302 |
303 | /// The result of the test.
304 | final TestResult result;
305 |
306 | /// Whether the test's result should be hidden.
307 | final bool hidden;
308 |
309 | /// Whether the test (or some part of it) was skipped.
310 | final bool skipped;
311 | }
312 |
313 | /// {@template done_test_event}
314 | /// An event indicating the result of the entire test run.
315 | /// This will be the final event emitted by the reporter.
316 | /// {@endtemplate}
317 | @JsonSerializable()
318 | class DoneTestEvent extends TestEvent {
319 | /// {@macro done_test_event}
320 | const DoneTestEvent({
321 | required this.success,
322 | required super.time,
323 | }) : super(type: 'done');
324 |
325 | /// {@macro done_test_event}
326 | factory DoneTestEvent.fromJson(Map json) =>
327 | _$DoneTestEventFromJson(json);
328 |
329 | /// Whether all tests succeeded (or were skipped).
330 | ///
331 | /// Will be `null` if the test runner was close before all tests completed
332 | /// running.
333 | final bool? success;
334 | }
335 |
336 | /// {@template test_exit_event}
337 | /// An event emitted when a test completes.
338 | /// The [exitCode] attribute indicates the result of the test process.
339 | /// {@endtemplate}
340 | @JsonSerializable()
341 | class ExitTestEvent extends TestEvent {
342 | /// {@macro test_exit_event}
343 | const ExitTestEvent({required super.time, required this.exitCode})
344 | : super(type: 'exit');
345 |
346 | /// {@macro test_exit_event}
347 | factory ExitTestEvent.fromJson(Map json) =>
348 | _$ExitTestEventFromJson(json);
349 |
350 | /// The exit code associated with the test process.
351 | final int exitCode;
352 | }
353 |
354 | /// {@template test_suite}
355 | /// A test suite corresponding to a loaded test file.
356 | /// The suite's ID is unique in the context of this test run.
357 | /// It's used elsewhere in the protocol to refer to this suite
358 | /// without including its full representation.
359 |
360 | /// A suite's platform is one of the platforms that can be passed to the
361 | /// --platform option, or null if there is no platform
362 | /// (for example if the file doesn't exist at all).
363 | /// Its path is either absolute or relative to the root of the current package.
364 | /// {@endtemplate}
365 | @JsonSerializable()
366 | class TestSuite {
367 | /// {@macro test_suite}
368 | const TestSuite({
369 | required this.id,
370 | required this.platform,
371 | this.path,
372 | });
373 |
374 | /// {@macro test_suite}
375 | factory TestSuite.fromJson(Map json) =>
376 | _$TestSuiteFromJson(json);
377 |
378 | /// An opaque ID for the group.
379 | final int id;
380 |
381 | /// The platform on which the suite is running.
382 | final String platform;
383 |
384 | /// The path to the suite's file, or `null` if that path is unknown.
385 | final String? path;
386 | }
387 |
388 | /// {@template test_group}
389 | /// A group containing test cases.
390 | /// The group's ID is unique in the context of this test run.
391 | /// It's used elsewhere in the protocol to refer to this group
392 | /// without including its full representation.
393 | /// {@endtemplate}
394 | @JsonSerializable()
395 | class TestGroup {
396 | /// {@macro test_group}
397 | const TestGroup({
398 | required this.id,
399 | required this.name,
400 | required this.suiteID,
401 | required this.testCount,
402 | required this.metadata,
403 | this.parentID,
404 | this.line,
405 | this.column,
406 | this.url,
407 | });
408 |
409 | /// {@macro test_group}
410 | factory TestGroup.fromJson(Map json) =>
411 | _$TestGroupFromJson(json);
412 |
413 | /// An opaque ID for the group.
414 | final int id;
415 |
416 | /// The name of the group, including prefixes from any containing groups.
417 | final String name;
418 |
419 | /// The ID of the suite containing this group.
420 | final int suiteID;
421 |
422 | /// The ID of the group's parent group, unless it's the root group.
423 | final int? parentID;
424 |
425 | /// The number of tests (recursively) within this group.
426 | final int testCount;
427 |
428 | /// The (1-based) line on which the group was defined, or `null`.
429 | final int? line;
430 |
431 | /// The (1-based) column on which the group was defined, or `null`.
432 | final int? column;
433 |
434 | /// The URL for the file in which the group was defined, or `null`.
435 | final String? url;
436 |
437 | /// This field is deprecated and should not be used.
438 | final TestMetadata metadata;
439 | }
440 |
441 | /// {@template test_metadata}
442 | /// Test metadata regarding whether the test was skipped and the reason.
443 | /// {@endtemplate}
444 | @JsonSerializable()
445 | class TestMetadata {
446 | /// {@macro test_metadata}
447 | TestMetadata({required this.skip, this.skipReason});
448 |
449 | /// {@macro test_metadata}
450 | factory TestMetadata.fromJson(Map json) =>
451 | _$TestMetadataFromJson(json);
452 |
453 | /// Whether the test was skipped.
454 | final bool skip;
455 |
456 | /// The reason the tests was skipped, or `null` if it wasn't skipped.
457 | final String? skipReason;
458 | }
459 |
460 | /// {@template test}
461 | /// A single test case. The test's ID is unique in the context of this test run.
462 | /// It's used elsewhere in the protocol to refer to this test
463 | /// without including its full representation.
464 | /// {@endtemplate}
465 | @JsonSerializable()
466 | class Test {
467 | /// {@macro test}
468 | Test({
469 | required this.id,
470 | required this.name,
471 | required this.suiteID,
472 | required this.groupIDs,
473 | required this.metadata,
474 | this.line,
475 | this.column,
476 | this.url,
477 | this.rootLine,
478 | this.rootColumn,
479 | this.rootUrl,
480 | });
481 |
482 | /// {@macro test}
483 | factory Test.fromJson(Map json) => _$TestFromJson(json);
484 |
485 | /// An opaque ID for the test.
486 | final int id;
487 |
488 | /// The name of the test, including prefixes from any containing groups.
489 | final String name;
490 |
491 | /// The ID of the suite containing this test.
492 | final int suiteID;
493 |
494 | /// The IDs of groups containing this test, in order from outermost to
495 | /// innermost.
496 | final List groupIDs;
497 |
498 | /// The (1-based) line on which the test was defined, or `null`.
499 | final int? line;
500 |
501 | /// The (1-based) column on which the test was defined, or `null`.
502 | final int? column;
503 |
504 | /// The URL for the file in which the test was defined, or `null`.
505 | final String? url;
506 |
507 | /// The (1-based) line in the original test suite from which the test
508 | /// originated.
509 | ///
510 | /// Will only be present if `root_url` is different from `url`.
511 | final int? rootLine;
512 |
513 | /// The (1-based) line on in the original test suite from which the test
514 | /// originated.
515 | ///
516 | /// Will only be present if `root_url` is different from `url`.
517 | final int? rootColumn;
518 |
519 | /// The URL for the original test suite in which the test was defined.
520 | ///
521 | /// Will only be present if different from `url`.
522 | final String? rootUrl;
523 |
524 | /// This field is deprecated and should not be used.
525 | final TestMetadata metadata;
526 | }
527 |
--------------------------------------------------------------------------------
/lib/src/models/test_event.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: document_ignores, implicit_dynamic_parameter, cast_nullable_to_non_nullable, require_trailing_commas, lines_longer_than_80_chars
4 |
5 | part of 'test_event.dart';
6 |
7 | // **************************************************************************
8 | // JsonSerializableGenerator
9 | // **************************************************************************
10 |
11 | StartTestEvent _$StartTestEventFromJson(Map json) =>
12 | $checkedCreate(
13 | 'StartTestEvent',
14 | json,
15 | ($checkedConvert) {
16 | final val = StartTestEvent(
17 | protocolVersion:
18 | $checkedConvert('protocolVersion', (v) => v as String),
19 | runnerVersion: $checkedConvert('runnerVersion', (v) => v as String?),
20 | pid: $checkedConvert('pid', (v) => (v as num).toInt()),
21 | time: $checkedConvert('time', (v) => (v as num).toInt()),
22 | );
23 | return val;
24 | },
25 | );
26 |
27 | AllSuitesTestEvent _$AllSuitesTestEventFromJson(Map json) =>
28 | $checkedCreate(
29 | 'AllSuitesTestEvent',
30 | json,
31 | ($checkedConvert) {
32 | final val = AllSuitesTestEvent(
33 | count: $checkedConvert('count', (v) => (v as num).toInt()),
34 | time: $checkedConvert('time', (v) => (v as num).toInt()),
35 | );
36 | return val;
37 | },
38 | );
39 |
40 | SuiteTestEvent _$SuiteTestEventFromJson(Map json) =>
41 | $checkedCreate(
42 | 'SuiteTestEvent',
43 | json,
44 | ($checkedConvert) {
45 | final val = SuiteTestEvent(
46 | suite: $checkedConvert(
47 | 'suite', (v) => TestSuite.fromJson(v as Map)),
48 | time: $checkedConvert('time', (v) => (v as num).toInt()),
49 | );
50 | return val;
51 | },
52 | );
53 |
54 | DebugTestEvent _$DebugTestEventFromJson(Map json) =>
55 | $checkedCreate(
56 | 'DebugTestEvent',
57 | json,
58 | ($checkedConvert) {
59 | final val = DebugTestEvent(
60 | suiteID: $checkedConvert('suiteID', (v) => (v as num).toInt()),
61 | observatory: $checkedConvert('observatory', (v) => v as String?),
62 | remoteDebugger:
63 | $checkedConvert('remoteDebugger', (v) => v as String?),
64 | time: $checkedConvert('time', (v) => (v as num).toInt()),
65 | );
66 | return val;
67 | },
68 | );
69 |
70 | GroupTestEvent _$GroupTestEventFromJson(Map json) =>
71 | $checkedCreate(
72 | 'GroupTestEvent',
73 | json,
74 | ($checkedConvert) {
75 | final val = GroupTestEvent(
76 | group: $checkedConvert(
77 | 'group', (v) => TestGroup.fromJson(v as Map)),
78 | time: $checkedConvert('time', (v) => (v as num).toInt()),
79 | );
80 | return val;
81 | },
82 | );
83 |
84 | TestStartEvent _$TestStartEventFromJson(Map json) =>
85 | $checkedCreate(
86 | 'TestStartEvent',
87 | json,
88 | ($checkedConvert) {
89 | final val = TestStartEvent(
90 | test: $checkedConvert(
91 | 'test', (v) => Test.fromJson(v as Map)),
92 | time: $checkedConvert('time', (v) => (v as num).toInt()),
93 | );
94 | return val;
95 | },
96 | );
97 |
98 | MessageTestEvent _$MessageTestEventFromJson(Map json) =>
99 | $checkedCreate(
100 | 'MessageTestEvent',
101 | json,
102 | ($checkedConvert) {
103 | final val = MessageTestEvent(
104 | testID: $checkedConvert('testID', (v) => (v as num).toInt()),
105 | messageType: $checkedConvert('messageType', (v) => v as String),
106 | message: $checkedConvert('message', (v) => v as String),
107 | time: $checkedConvert('time', (v) => (v as num).toInt()),
108 | );
109 | return val;
110 | },
111 | );
112 |
113 | ErrorTestEvent _$ErrorTestEventFromJson(Map json) =>
114 | $checkedCreate(
115 | 'ErrorTestEvent',
116 | json,
117 | ($checkedConvert) {
118 | final val = ErrorTestEvent(
119 | testID: $checkedConvert('testID', (v) => (v as num).toInt()),
120 | error: $checkedConvert('error', (v) => v as String),
121 | stackTrace: $checkedConvert('stackTrace', (v) => v as String),
122 | isFailure: $checkedConvert('isFailure', (v) => v as bool),
123 | time: $checkedConvert('time', (v) => (v as num).toInt()),
124 | );
125 | return val;
126 | },
127 | );
128 |
129 | TestDoneEvent _$TestDoneEventFromJson(Map json) =>
130 | $checkedCreate(
131 | 'TestDoneEvent',
132 | json,
133 | ($checkedConvert) {
134 | final val = TestDoneEvent(
135 | testID: $checkedConvert('testID', (v) => (v as num).toInt()),
136 | result: $checkedConvert(
137 | 'result', (v) => $enumDecode(_$TestResultEnumMap, v)),
138 | hidden: $checkedConvert('hidden', (v) => v as bool),
139 | skipped: $checkedConvert('skipped', (v) => v as bool),
140 | time: $checkedConvert('time', (v) => (v as num).toInt()),
141 | );
142 | return val;
143 | },
144 | );
145 |
146 | const _$TestResultEnumMap = {
147 | TestResult.success: 'success',
148 | TestResult.failure: 'failure',
149 | TestResult.error: 'error',
150 | };
151 |
152 | DoneTestEvent _$DoneTestEventFromJson(Map json) =>
153 | $checkedCreate(
154 | 'DoneTestEvent',
155 | json,
156 | ($checkedConvert) {
157 | final val = DoneTestEvent(
158 | success: $checkedConvert('success', (v) => v as bool?),
159 | time: $checkedConvert('time', (v) => (v as num).toInt()),
160 | );
161 | return val;
162 | },
163 | );
164 |
165 | ExitTestEvent _$ExitTestEventFromJson(Map json) =>
166 | $checkedCreate(
167 | 'ExitTestEvent',
168 | json,
169 | ($checkedConvert) {
170 | final val = ExitTestEvent(
171 | time: $checkedConvert('time', (v) => (v as num).toInt()),
172 | exitCode: $checkedConvert('exitCode', (v) => (v as num).toInt()),
173 | );
174 | return val;
175 | },
176 | );
177 |
178 | TestSuite _$TestSuiteFromJson(Map json) => $checkedCreate(
179 | 'TestSuite',
180 | json,
181 | ($checkedConvert) {
182 | final val = TestSuite(
183 | id: $checkedConvert('id', (v) => (v as num).toInt()),
184 | platform: $checkedConvert('platform', (v) => v as String),
185 | path: $checkedConvert('path', (v) => v as String?),
186 | );
187 | return val;
188 | },
189 | );
190 |
191 | TestGroup _$TestGroupFromJson(Map json) => $checkedCreate(
192 | 'TestGroup',
193 | json,
194 | ($checkedConvert) {
195 | final val = TestGroup(
196 | id: $checkedConvert('id', (v) => (v as num).toInt()),
197 | name: $checkedConvert('name', (v) => v as String),
198 | suiteID: $checkedConvert('suiteID', (v) => (v as num).toInt()),
199 | testCount: $checkedConvert('testCount', (v) => (v as num).toInt()),
200 | metadata: $checkedConvert('metadata',
201 | (v) => TestMetadata.fromJson(v as Map)),
202 | parentID: $checkedConvert('parentID', (v) => (v as num?)?.toInt()),
203 | line: $checkedConvert('line', (v) => (v as num?)?.toInt()),
204 | column: $checkedConvert('column', (v) => (v as num?)?.toInt()),
205 | url: $checkedConvert('url', (v) => v as String?),
206 | );
207 | return val;
208 | },
209 | );
210 |
211 | TestMetadata _$TestMetadataFromJson(Map json) =>
212 | $checkedCreate(
213 | 'TestMetadata',
214 | json,
215 | ($checkedConvert) {
216 | final val = TestMetadata(
217 | skip: $checkedConvert('skip', (v) => v as bool),
218 | skipReason: $checkedConvert('skipReason', (v) => v as String?),
219 | );
220 | return val;
221 | },
222 | );
223 |
224 | Test _$TestFromJson(Map json) => $checkedCreate(
225 | 'Test',
226 | json,
227 | ($checkedConvert) {
228 | final val = Test(
229 | id: $checkedConvert('id', (v) => (v as num).toInt()),
230 | name: $checkedConvert('name', (v) => v as String),
231 | suiteID: $checkedConvert('suiteID', (v) => (v as num).toInt()),
232 | groupIDs: $checkedConvert(
233 | 'groupIDs',
234 | (v) =>
235 | (v as List).map((e) => (e as num).toInt()).toList()),
236 | metadata: $checkedConvert('metadata',
237 | (v) => TestMetadata.fromJson(v as Map)),
238 | line: $checkedConvert('line', (v) => (v as num?)?.toInt()),
239 | column: $checkedConvert('column', (v) => (v as num?)?.toInt()),
240 | url: $checkedConvert('url', (v) => v as String?),
241 | rootLine: $checkedConvert('rootLine', (v) => (v as num?)?.toInt()),
242 | rootColumn:
243 | $checkedConvert('rootColumn', (v) => (v as num?)?.toInt()),
244 | rootUrl: $checkedConvert('rootUrl', (v) => v as String?),
245 | );
246 | return val;
247 | },
248 | );
249 |
--------------------------------------------------------------------------------
/lib/src/very_good_test_runner.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:universal_io/io.dart';
5 | import 'package:very_good_test_runner/very_good_test_runner.dart';
6 |
7 | /// Signature for `Process.start`.
8 | typedef StartProcess = Future Function(
9 | String executable,
10 | List arguments, {
11 | String? workingDirectory,
12 | Map? environment,
13 | bool includeParentEnvironment,
14 | bool runInShell,
15 | ProcessStartMode mode,
16 | });
17 |
18 | /// Runs `dart test` and returns a stream of [TestEvent]
19 | /// reported by the process.
20 | ///
21 | /// ```dart
22 | /// void main() {
23 | /// // React to `TestEvent` instances.
24 | /// dartTest().listen(print);
25 | /// }
26 | /// ```
27 | Stream dartTest({
28 | List? arguments,
29 | String? workingDirectory,
30 | Map? environment,
31 | bool runInShell = false,
32 | StartProcess startProcess = Process.start,
33 | }) {
34 | return _runTestProcess(
35 | () => startProcess(
36 | 'dart',
37 | ['test', ...?arguments, '--reporter=json'],
38 | environment: environment,
39 | workingDirectory: workingDirectory,
40 | runInShell: runInShell,
41 | ),
42 | );
43 | }
44 |
45 | /// Runs `flutter test` and returns a stream of [TestEvent]
46 | /// reported by the process.
47 | ///
48 | /// ```dart
49 | /// void main() {
50 | /// // React to `TestEvent` instances.
51 | /// flutterTest().listen(print);
52 | /// }
53 | /// ```
54 | Stream flutterTest({
55 | List? arguments,
56 | String? workingDirectory,
57 | Map? environment,
58 | bool runInShell = false,
59 | StartProcess startProcess = Process.start,
60 | }) {
61 | return _runTestProcess(
62 | () => startProcess(
63 | 'flutter',
64 | ['test', ...?arguments, '--reporter=json'],
65 | environment: environment,
66 | workingDirectory: workingDirectory,
67 | runInShell: runInShell,
68 | ),
69 | );
70 | }
71 |
72 | Stream _runTestProcess(
73 | Future Function() processRunner,
74 | ) {
75 | final controller = StreamController();
76 | late StreamSubscription testEventSubscription;
77 | late StreamSubscription errorSubscription;
78 | late Future processFuture;
79 |
80 | Future onListen() async {
81 | final stopwatch = Stopwatch()..start();
82 | processFuture = processRunner();
83 | final process = await processFuture;
84 | final errors = process.stderr.map((e) => utf8.decode(e).trim());
85 | final testEvents = process.stdout.mapToTestEvents();
86 | errorSubscription = errors.listen(controller.addError);
87 | testEventSubscription = testEvents.listen(
88 | controller.add,
89 | onError: controller.addError,
90 | );
91 |
92 | final exitCode = await process.exitCode;
93 | stopwatch.stop();
94 | await Future.wait([
95 | errorSubscription.cancel(),
96 | testEventSubscription.cancel(),
97 | ]);
98 | if (controller.isClosed) return;
99 | controller.add(
100 | ExitTestEvent(
101 | time: stopwatch.elapsedMilliseconds,
102 | exitCode: exitCode,
103 | ),
104 | );
105 | await controller.close();
106 | }
107 |
108 | Future onCancel() async {
109 | await controller.close();
110 | (await processFuture).kill();
111 | await errorSubscription.cancel();
112 | await testEventSubscription.cancel();
113 | }
114 |
115 | controller
116 | ..onListen = onListen
117 | ..onCancel = onCancel;
118 |
119 | return controller.stream;
120 | }
121 |
122 | extension on Stream> {
123 | Stream mapToTestEvents() {
124 | return map(utf8.decode)
125 | .expand(_splitLines)
126 | .map