flakestat 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flakestat might be problematic. Click here for more details.
- flakestat-0.1.0/PKG-INFO +95 -0
- flakestat-0.1.0/README.md +76 -0
- flakestat-0.1.0/flakestat/__init__.py +37 -0
- flakestat-0.1.0/flakestat/__main__.py +8 -0
- flakestat-0.1.0/flakestat/_binary.py +184 -0
- flakestat-0.1.0/flakestat.egg-info/PKG-INFO +95 -0
- flakestat-0.1.0/flakestat.egg-info/SOURCES.txt +10 -0
- flakestat-0.1.0/flakestat.egg-info/dependency_links.txt +1 -0
- flakestat-0.1.0/flakestat.egg-info/entry_points.txt +2 -0
- flakestat-0.1.0/flakestat.egg-info/top_level.txt +1 -0
- flakestat-0.1.0/pyproject.toml +47 -0
- flakestat-0.1.0/setup.cfg +4 -0
flakestat-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flakestat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find flaky tests in any language - pytest, Jest, go test, JUnit. Local CLI, no SaaS, no account.
|
|
5
|
+
Author: flakestat contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rowhitswami/flakestat
|
|
8
|
+
Project-URL: Issues, https://github.com/rowhitswami/flakestat/issues
|
|
9
|
+
Project-URL: Source, https://github.com/rowhitswami/flakestat
|
|
10
|
+
Keywords: flaky,flaky-tests,flaky-test-detection,test-flakiness,flake-detection,test-stability,pytest,testing,junit,ci,quarantine
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# flakestat
|
|
21
|
+
|
|
22
|
+
Find flaky tests in any language. No SaaS, no account, no data leaving your machine.
|
|
23
|
+
|
|
24
|
+
A flaky test passes and fails randomly **on the same code**. flakestat tells you
|
|
25
|
+
exactly which ones, ranked worst first.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
flakestat hunt --runs 20 \
|
|
29
|
+
--junit 'reports/junit-{run}.xml' \
|
|
30
|
+
-- pytest -q --junitxml='reports/junit-{run}.xml'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
VERDICT SCORE RUNS PASS/FAIL TEST
|
|
35
|
+
flaky 0.62 20 14/6 tests.test_auth::test_refresh_token
|
|
36
|
+
consistently-failing 0.00 20 0/20 tests.test_billing::test_discount
|
|
37
|
+
|
|
38
|
+
1 flaky, 0 suspect, 1 consistently failing, 1 stable, 1 unscored (of 4 tests)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install flakestat
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This package downloads the prebuilt `flakestat` binary for your platform
|
|
48
|
+
(macOS, Linux, Windows on x86_64/arm64) from GitHub Releases on first use. It has
|
|
49
|
+
no Python dependencies, so it cannot interfere with your project's resolution.
|
|
50
|
+
|
|
51
|
+
## Why the verdicts matter
|
|
52
|
+
|
|
53
|
+
**Flakiness is inconsistency, not failure.** A test that fails 100% of the time
|
|
54
|
+
scores 1.0 on failure rate but isn't flaky — it's broken. flakestat scores
|
|
55
|
+
*state transitions* (pass→fail→pass), so an always-failing test correctly scores
|
|
56
|
+
zero and is reported separately as `consistently-failing` instead of being mixed
|
|
57
|
+
in with real flakes.
|
|
58
|
+
|
|
59
|
+
Skipped tests are excluded rather than treated as passes, so `@pytest.mark.skip`
|
|
60
|
+
never manufactures a phantom transition.
|
|
61
|
+
|
|
62
|
+
## How it compares
|
|
63
|
+
|
|
64
|
+
`pytest-rerunfailures` and `pytest-flakefinder` retry tests so a run goes green.
|
|
65
|
+
That hides the symptom. flakestat *measures* which tests are unreliable and by
|
|
66
|
+
how much, so they can be fixed — and it works across every suite in a polyglot
|
|
67
|
+
repo, not just the Python one.
|
|
68
|
+
|
|
69
|
+
## Tracking over time
|
|
70
|
+
|
|
71
|
+
A burst proves flakiness exists. History *measures* it, and catches
|
|
72
|
+
environment-dependent flakes a local burst never will.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
flakestat ingest 'reports/**/*.xml' # in CI, after tests
|
|
76
|
+
flakestat report --top 20
|
|
77
|
+
flakestat report --fail-on-flaky # exit 1 to gate a pipeline
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
History lives in `.flakestat/runs.ndjson` — one JSON object per line. Commit it
|
|
81
|
+
for shared team history, or keep it as a CI artifact.
|
|
82
|
+
|
|
83
|
+
## Environment variables
|
|
84
|
+
|
|
85
|
+
| Variable | Effect |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `FLAKESTAT_BINARY` | Use this binary instead of downloading |
|
|
88
|
+
| `FLAKESTAT_SKIP_DOWNLOAD` | Fail instead of downloading |
|
|
89
|
+
| `FLAKESTAT_CACHE_DIR` | Where the binary is cached |
|
|
90
|
+
|
|
91
|
+
## Full documentation
|
|
92
|
+
|
|
93
|
+
<https://github.com/rowhitswami/flakestat>
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# flakestat
|
|
2
|
+
|
|
3
|
+
Find flaky tests in any language. No SaaS, no account, no data leaving your machine.
|
|
4
|
+
|
|
5
|
+
A flaky test passes and fails randomly **on the same code**. flakestat tells you
|
|
6
|
+
exactly which ones, ranked worst first.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
flakestat hunt --runs 20 \
|
|
10
|
+
--junit 'reports/junit-{run}.xml' \
|
|
11
|
+
-- pytest -q --junitxml='reports/junit-{run}.xml'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
VERDICT SCORE RUNS PASS/FAIL TEST
|
|
16
|
+
flaky 0.62 20 14/6 tests.test_auth::test_refresh_token
|
|
17
|
+
consistently-failing 0.00 20 0/20 tests.test_billing::test_discount
|
|
18
|
+
|
|
19
|
+
1 flaky, 0 suspect, 1 consistently failing, 1 stable, 1 unscored (of 4 tests)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install flakestat
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This package downloads the prebuilt `flakestat` binary for your platform
|
|
29
|
+
(macOS, Linux, Windows on x86_64/arm64) from GitHub Releases on first use. It has
|
|
30
|
+
no Python dependencies, so it cannot interfere with your project's resolution.
|
|
31
|
+
|
|
32
|
+
## Why the verdicts matter
|
|
33
|
+
|
|
34
|
+
**Flakiness is inconsistency, not failure.** A test that fails 100% of the time
|
|
35
|
+
scores 1.0 on failure rate but isn't flaky — it's broken. flakestat scores
|
|
36
|
+
*state transitions* (pass→fail→pass), so an always-failing test correctly scores
|
|
37
|
+
zero and is reported separately as `consistently-failing` instead of being mixed
|
|
38
|
+
in with real flakes.
|
|
39
|
+
|
|
40
|
+
Skipped tests are excluded rather than treated as passes, so `@pytest.mark.skip`
|
|
41
|
+
never manufactures a phantom transition.
|
|
42
|
+
|
|
43
|
+
## How it compares
|
|
44
|
+
|
|
45
|
+
`pytest-rerunfailures` and `pytest-flakefinder` retry tests so a run goes green.
|
|
46
|
+
That hides the symptom. flakestat *measures* which tests are unreliable and by
|
|
47
|
+
how much, so they can be fixed — and it works across every suite in a polyglot
|
|
48
|
+
repo, not just the Python one.
|
|
49
|
+
|
|
50
|
+
## Tracking over time
|
|
51
|
+
|
|
52
|
+
A burst proves flakiness exists. History *measures* it, and catches
|
|
53
|
+
environment-dependent flakes a local burst never will.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
flakestat ingest 'reports/**/*.xml' # in CI, after tests
|
|
57
|
+
flakestat report --top 20
|
|
58
|
+
flakestat report --fail-on-flaky # exit 1 to gate a pipeline
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
History lives in `.flakestat/runs.ndjson` — one JSON object per line. Commit it
|
|
62
|
+
for shared team history, or keep it as a CI artifact.
|
|
63
|
+
|
|
64
|
+
## Environment variables
|
|
65
|
+
|
|
66
|
+
| Variable | Effect |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `FLAKESTAT_BINARY` | Use this binary instead of downloading |
|
|
69
|
+
| `FLAKESTAT_SKIP_DOWNLOAD` | Fail instead of downloading |
|
|
70
|
+
| `FLAKESTAT_CACHE_DIR` | Where the binary is cached |
|
|
71
|
+
|
|
72
|
+
## Full documentation
|
|
73
|
+
|
|
74
|
+
<https://github.com/rowhitswami/flakestat>
|
|
75
|
+
|
|
76
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""flakestat - find flaky tests in any language.
|
|
2
|
+
|
|
3
|
+
This package is a thin wrapper that fetches and runs the flakestat binary.
|
|
4
|
+
The tool itself is written in Go; see https://github.com/rowhitswami/flakestat.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from ._binary import InstallError, ensure_binary
|
|
11
|
+
|
|
12
|
+
# Kept in step with the released binary by CI at publish time.
|
|
13
|
+
__version__ = "0.0.0"
|
|
14
|
+
|
|
15
|
+
__all__ = ["main", "ensure_binary", "InstallError", "__version__"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> int:
|
|
19
|
+
"""Console-script entry point: exec the binary, preserving the exit code."""
|
|
20
|
+
try:
|
|
21
|
+
binary = ensure_binary()
|
|
22
|
+
except InstallError as exc:
|
|
23
|
+
print(str(exc), file=sys.stderr)
|
|
24
|
+
return 1
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# CI gates depend on the exit code, so pass it straight through.
|
|
28
|
+
return subprocess.call([str(binary)] + sys.argv[1:])
|
|
29
|
+
except KeyboardInterrupt:
|
|
30
|
+
return 130
|
|
31
|
+
except OSError as exc:
|
|
32
|
+
print("flakestat: {}".format(exc), file=sys.stderr)
|
|
33
|
+
return 1
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
sys.exit(main())
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Resolve the flakestat binary, downloading it on first use.
|
|
2
|
+
|
|
3
|
+
pip has no post-install hook, and building a Go binary at install time would
|
|
4
|
+
require a Go toolchain on the user's machine. So the binary is fetched from
|
|
5
|
+
GitHub Releases the first time it is needed and cached per version.
|
|
6
|
+
|
|
7
|
+
Everything here is stdlib: the wrapper must never interfere with a user's
|
|
8
|
+
dependency resolution.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import os
|
|
13
|
+
import platform
|
|
14
|
+
import shutil
|
|
15
|
+
import stat
|
|
16
|
+
import sys
|
|
17
|
+
import tarfile
|
|
18
|
+
import tempfile
|
|
19
|
+
import urllib.error
|
|
20
|
+
import urllib.request
|
|
21
|
+
import zipfile
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
OWNER = "rowhitswami"
|
|
25
|
+
REPO = "flakestat"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InstallError(RuntimeError):
|
|
29
|
+
"""Raised when the binary cannot be obtained."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _version() -> str:
|
|
33
|
+
from . import __version__
|
|
34
|
+
|
|
35
|
+
return __version__
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _target():
|
|
39
|
+
"""Map this interpreter's platform onto the release archive naming."""
|
|
40
|
+
system = platform.system().lower()
|
|
41
|
+
goos = {"darwin": "darwin", "linux": "linux", "windows": "windows"}.get(system)
|
|
42
|
+
|
|
43
|
+
machine = platform.machine().lower()
|
|
44
|
+
goarch = {
|
|
45
|
+
"x86_64": "amd64",
|
|
46
|
+
"amd64": "amd64",
|
|
47
|
+
"aarch64": "arm64",
|
|
48
|
+
"arm64": "arm64",
|
|
49
|
+
}.get(machine)
|
|
50
|
+
|
|
51
|
+
if not goos or not goarch:
|
|
52
|
+
raise InstallError(
|
|
53
|
+
"flakestat: unsupported platform {}/{}.\n"
|
|
54
|
+
"Prebuilt binaries cover macOS, Linux and Windows on x86_64 and arm64.\n"
|
|
55
|
+
"Build from source instead:\n"
|
|
56
|
+
" go install github.com/{}/{}/cmd/{}@latest".format(
|
|
57
|
+
system, machine, OWNER, REPO, REPO
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return goos, goarch
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _cache_dir() -> Path:
|
|
65
|
+
"""Per-version cache, so upgrading the wrapper fetches a matching binary."""
|
|
66
|
+
if os.environ.get("FLAKESTAT_CACHE_DIR"):
|
|
67
|
+
base = Path(os.environ["FLAKESTAT_CACHE_DIR"])
|
|
68
|
+
elif sys.platform == "win32":
|
|
69
|
+
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
70
|
+
elif sys.platform == "darwin":
|
|
71
|
+
base = Path.home() / "Library" / "Caches"
|
|
72
|
+
else:
|
|
73
|
+
base = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
|
|
74
|
+
|
|
75
|
+
return base / "flakestat" / _version()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _binary_name() -> str:
|
|
79
|
+
return "flakestat.exe" if sys.platform == "win32" else "flakestat"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _fetch(url: str) -> bytes:
|
|
83
|
+
try:
|
|
84
|
+
with urllib.request.urlopen(url) as resp: # noqa: S310 - fixed https host
|
|
85
|
+
return resp.read()
|
|
86
|
+
except urllib.error.HTTPError as exc:
|
|
87
|
+
raise InstallError("flakestat: GET {} -> {}".format(url, exc.code)) from exc
|
|
88
|
+
except urllib.error.URLError as exc:
|
|
89
|
+
raise InstallError(
|
|
90
|
+
"flakestat: could not reach {} ({})".format(url, exc.reason)
|
|
91
|
+
) from exc
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _verify(archive_name: str, blob: bytes, base_url: str) -> None:
|
|
95
|
+
try:
|
|
96
|
+
sums = _fetch("{}/checksums.txt".format(base_url)).decode("utf-8")
|
|
97
|
+
except InstallError:
|
|
98
|
+
print("flakestat: checksums.txt unavailable, skipping verification", file=sys.stderr)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
expected = None
|
|
102
|
+
for line in sums.splitlines():
|
|
103
|
+
if line.strip().endswith(archive_name):
|
|
104
|
+
expected = line.split()[0]
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
if expected is None:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
actual = hashlib.sha256(blob).hexdigest()
|
|
111
|
+
if expected != actual:
|
|
112
|
+
raise InstallError(
|
|
113
|
+
"flakestat: checksum mismatch for {}\n expected: {}\n actual: {}".format(
|
|
114
|
+
archive_name, expected, actual
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _download(dest: Path) -> None:
|
|
120
|
+
goos, goarch = _target()
|
|
121
|
+
version = _version()
|
|
122
|
+
ext = "zip" if goos == "windows" else "tar.gz"
|
|
123
|
+
|
|
124
|
+
archive = "{}_{}_{}_{}.{}".format(REPO, version, goos, goarch, ext)
|
|
125
|
+
base_url = "https://github.com/{}/{}/releases/download/v{}".format(OWNER, REPO, version)
|
|
126
|
+
|
|
127
|
+
print(
|
|
128
|
+
"flakestat: downloading {} v{} ({}/{})".format(REPO, version, goos, goarch),
|
|
129
|
+
file=sys.stderr,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
blob = _fetch("{}/{}".format(base_url, archive))
|
|
133
|
+
_verify(archive, blob, base_url)
|
|
134
|
+
|
|
135
|
+
name = _binary_name()
|
|
136
|
+
|
|
137
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
138
|
+
tmp_path = Path(tmp)
|
|
139
|
+
archive_path = tmp_path / archive
|
|
140
|
+
archive_path.write_bytes(blob)
|
|
141
|
+
|
|
142
|
+
if ext == "zip":
|
|
143
|
+
with zipfile.ZipFile(archive_path) as zf:
|
|
144
|
+
zf.extract(name, tmp_path)
|
|
145
|
+
else:
|
|
146
|
+
with tarfile.open(archive_path) as tf:
|
|
147
|
+
member = tf.getmember(name)
|
|
148
|
+
# Refuse absolute paths and traversal in archive members.
|
|
149
|
+
if member.name != name or Path(member.name).is_absolute():
|
|
150
|
+
raise InstallError("flakestat: unexpected archive member {}".format(member.name))
|
|
151
|
+
tf.extract(member, tmp_path)
|
|
152
|
+
|
|
153
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
|
|
155
|
+
# Move into place via a temporary name so concurrent installs cannot
|
|
156
|
+
# observe a half-written binary.
|
|
157
|
+
staged = dest.parent / (dest.name + ".tmp")
|
|
158
|
+
shutil.move(str(tmp_path / name), str(staged))
|
|
159
|
+
staged.chmod(staged.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
160
|
+
os.replace(str(staged), str(dest))
|
|
161
|
+
|
|
162
|
+
print("flakestat: installed to {}".format(dest), file=sys.stderr)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def ensure_binary() -> Path:
|
|
166
|
+
"""Return the path to the binary, downloading it if needed."""
|
|
167
|
+
override = os.environ.get("FLAKESTAT_BINARY")
|
|
168
|
+
if override:
|
|
169
|
+
path = Path(override)
|
|
170
|
+
if not path.exists():
|
|
171
|
+
raise InstallError("flakestat: FLAKESTAT_BINARY={} does not exist".format(override))
|
|
172
|
+
return path
|
|
173
|
+
|
|
174
|
+
dest = _cache_dir() / _binary_name()
|
|
175
|
+
if dest.exists():
|
|
176
|
+
return dest
|
|
177
|
+
|
|
178
|
+
if os.environ.get("FLAKESTAT_SKIP_DOWNLOAD"):
|
|
179
|
+
raise InstallError(
|
|
180
|
+
"flakestat: binary missing and FLAKESTAT_SKIP_DOWNLOAD is set"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
_download(dest)
|
|
184
|
+
return dest
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flakestat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find flaky tests in any language - pytest, Jest, go test, JUnit. Local CLI, no SaaS, no account.
|
|
5
|
+
Author: flakestat contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rowhitswami/flakestat
|
|
8
|
+
Project-URL: Issues, https://github.com/rowhitswami/flakestat/issues
|
|
9
|
+
Project-URL: Source, https://github.com/rowhitswami/flakestat
|
|
10
|
+
Keywords: flaky,flaky-tests,flaky-test-detection,test-flakiness,flake-detection,test-stability,pytest,testing,junit,ci,quarantine
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# flakestat
|
|
21
|
+
|
|
22
|
+
Find flaky tests in any language. No SaaS, no account, no data leaving your machine.
|
|
23
|
+
|
|
24
|
+
A flaky test passes and fails randomly **on the same code**. flakestat tells you
|
|
25
|
+
exactly which ones, ranked worst first.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
flakestat hunt --runs 20 \
|
|
29
|
+
--junit 'reports/junit-{run}.xml' \
|
|
30
|
+
-- pytest -q --junitxml='reports/junit-{run}.xml'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
VERDICT SCORE RUNS PASS/FAIL TEST
|
|
35
|
+
flaky 0.62 20 14/6 tests.test_auth::test_refresh_token
|
|
36
|
+
consistently-failing 0.00 20 0/20 tests.test_billing::test_discount
|
|
37
|
+
|
|
38
|
+
1 flaky, 0 suspect, 1 consistently failing, 1 stable, 1 unscored (of 4 tests)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install flakestat
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This package downloads the prebuilt `flakestat` binary for your platform
|
|
48
|
+
(macOS, Linux, Windows on x86_64/arm64) from GitHub Releases on first use. It has
|
|
49
|
+
no Python dependencies, so it cannot interfere with your project's resolution.
|
|
50
|
+
|
|
51
|
+
## Why the verdicts matter
|
|
52
|
+
|
|
53
|
+
**Flakiness is inconsistency, not failure.** A test that fails 100% of the time
|
|
54
|
+
scores 1.0 on failure rate but isn't flaky — it's broken. flakestat scores
|
|
55
|
+
*state transitions* (pass→fail→pass), so an always-failing test correctly scores
|
|
56
|
+
zero and is reported separately as `consistently-failing` instead of being mixed
|
|
57
|
+
in with real flakes.
|
|
58
|
+
|
|
59
|
+
Skipped tests are excluded rather than treated as passes, so `@pytest.mark.skip`
|
|
60
|
+
never manufactures a phantom transition.
|
|
61
|
+
|
|
62
|
+
## How it compares
|
|
63
|
+
|
|
64
|
+
`pytest-rerunfailures` and `pytest-flakefinder` retry tests so a run goes green.
|
|
65
|
+
That hides the symptom. flakestat *measures* which tests are unreliable and by
|
|
66
|
+
how much, so they can be fixed — and it works across every suite in a polyglot
|
|
67
|
+
repo, not just the Python one.
|
|
68
|
+
|
|
69
|
+
## Tracking over time
|
|
70
|
+
|
|
71
|
+
A burst proves flakiness exists. History *measures* it, and catches
|
|
72
|
+
environment-dependent flakes a local burst never will.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
flakestat ingest 'reports/**/*.xml' # in CI, after tests
|
|
76
|
+
flakestat report --top 20
|
|
77
|
+
flakestat report --fail-on-flaky # exit 1 to gate a pipeline
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
History lives in `.flakestat/runs.ndjson` — one JSON object per line. Commit it
|
|
81
|
+
for shared team history, or keep it as a CI artifact.
|
|
82
|
+
|
|
83
|
+
## Environment variables
|
|
84
|
+
|
|
85
|
+
| Variable | Effect |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `FLAKESTAT_BINARY` | Use this binary instead of downloading |
|
|
88
|
+
| `FLAKESTAT_SKIP_DOWNLOAD` | Fail instead of downloading |
|
|
89
|
+
| `FLAKESTAT_CACHE_DIR` | Where the binary is cached |
|
|
90
|
+
|
|
91
|
+
## Full documentation
|
|
92
|
+
|
|
93
|
+
<https://github.com/rowhitswami/flakestat>
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
flakestat/__init__.py
|
|
4
|
+
flakestat/__main__.py
|
|
5
|
+
flakestat/_binary.py
|
|
6
|
+
flakestat.egg-info/PKG-INFO
|
|
7
|
+
flakestat.egg-info/SOURCES.txt
|
|
8
|
+
flakestat.egg-info/dependency_links.txt
|
|
9
|
+
flakestat.egg-info/entry_points.txt
|
|
10
|
+
flakestat.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flakestat
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "flakestat"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Find flaky tests in any language - pytest, Jest, go test, JUnit. Local CLI, no SaaS, no account."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "flakestat contributors" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"flaky",
|
|
15
|
+
"flaky-tests",
|
|
16
|
+
"flaky-test-detection",
|
|
17
|
+
"test-flakiness",
|
|
18
|
+
"flake-detection",
|
|
19
|
+
"test-stability",
|
|
20
|
+
"pytest",
|
|
21
|
+
"testing",
|
|
22
|
+
"junit",
|
|
23
|
+
"ci",
|
|
24
|
+
"quarantine",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 3 - Alpha",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Topic :: Software Development :: Testing",
|
|
32
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
33
|
+
]
|
|
34
|
+
# No runtime dependencies on purpose: everything used here is stdlib, so the
|
|
35
|
+
# wrapper cannot break a user's dependency resolution.
|
|
36
|
+
dependencies = []
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/rowhitswami/flakestat"
|
|
40
|
+
Issues = "https://github.com/rowhitswami/flakestat/issues"
|
|
41
|
+
Source = "https://github.com/rowhitswami/flakestat"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
flakestat = "flakestat:main"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools]
|
|
47
|
+
packages = ["flakestat"]
|