testing-conventions 0.0.2__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.
- testing_conventions-0.0.2/PKG-INFO +18 -0
- testing_conventions-0.0.2/README.md +7 -0
- testing_conventions-0.0.2/pyproject.toml +19 -0
- testing_conventions-0.0.2/rust/CHANGELOG.md +22 -0
- testing_conventions-0.0.2/rust/Cargo.lock +312 -0
- testing_conventions-0.0.2/rust/Cargo.toml +22 -0
- testing_conventions-0.0.2/rust/MIGRATIONS.md +43 -0
- testing_conventions-0.0.2/rust/src/config.rs +89 -0
- testing_conventions-0.0.2/rust/src/lib.rs +71 -0
- testing_conventions-0.0.2/rust/src/main.rs +14 -0
- testing_conventions-0.0.2/rust/tests/config_loader.rs +82 -0
- testing_conventions-0.0.2/rust/tests/fixtures/malformed.toml +4 -0
- testing_conventions-0.0.2/rust/tests/fixtures/unknown_key.toml +6 -0
- testing_conventions-0.0.2/rust/tests/fixtures/valid.toml +9 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: testing-conventions
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Enforce testing conventions in libraries (Python, TypeScript, and Rust).
|
|
5
|
+
Author-email: Kevin Scott <me@thekevinscott.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
9
|
+
Project-URL: Repository, https://github.com/thekevinscott/testing-conventions
|
|
10
|
+
|
|
11
|
+
# testing-conventions (Python)
|
|
12
|
+
|
|
13
|
+
Non-interactive agent CLI: spec in, code out, no review loop.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install testing-conventions
|
|
17
|
+
```
|
|
18
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.7,<2.0"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "testing-conventions"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Enforce testing conventions in libraries (Python, TypeScript, and Rust)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Kevin Scott", email = "me@thekevinscott.com" }]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Repository = "https://github.com/thekevinscott/testing-conventions"
|
|
16
|
+
|
|
17
|
+
[tool.maturin]
|
|
18
|
+
bindings = "bin"
|
|
19
|
+
manifest-path = "rust/Cargo.toml"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `config` module — `load_config()` parses one TOML config file into the new
|
|
11
|
+
in-memory `Config` schema (per-language `coverage` thresholds under the
|
|
12
|
+
`[python]` / `[typescript]` / `[rust]` tables) and self-validates on load:
|
|
13
|
+
unknown keys and malformed TOML are rejected. The parsed config is not
|
|
14
|
+
consumed yet. (#12)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
### Deprecated
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "anstream"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"anstyle",
|
|
12
|
+
"anstyle-parse",
|
|
13
|
+
"anstyle-query",
|
|
14
|
+
"anstyle-wincon",
|
|
15
|
+
"colorchoice",
|
|
16
|
+
"is_terminal_polyfill",
|
|
17
|
+
"utf8parse",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[[package]]
|
|
21
|
+
name = "anstyle"
|
|
22
|
+
version = "1.0.14"
|
|
23
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
+
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "anstyle-parse"
|
|
28
|
+
version = "1.0.0"
|
|
29
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
+
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"utf8parse",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "anstyle-query"
|
|
37
|
+
version = "1.1.5"
|
|
38
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
39
|
+
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
40
|
+
dependencies = [
|
|
41
|
+
"windows-sys",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "anstyle-wincon"
|
|
46
|
+
version = "3.0.11"
|
|
47
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
+
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
49
|
+
dependencies = [
|
|
50
|
+
"anstyle",
|
|
51
|
+
"once_cell_polyfill",
|
|
52
|
+
"windows-sys",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[[package]]
|
|
56
|
+
name = "anyhow"
|
|
57
|
+
version = "1.0.102"
|
|
58
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
59
|
+
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "clap"
|
|
63
|
+
version = "4.6.1"
|
|
64
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
65
|
+
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
|
66
|
+
dependencies = [
|
|
67
|
+
"clap_builder",
|
|
68
|
+
"clap_derive",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[[package]]
|
|
72
|
+
name = "clap_builder"
|
|
73
|
+
version = "4.6.0"
|
|
74
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
75
|
+
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
|
76
|
+
dependencies = [
|
|
77
|
+
"anstream",
|
|
78
|
+
"anstyle",
|
|
79
|
+
"clap_lex",
|
|
80
|
+
"strsim",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[[package]]
|
|
84
|
+
name = "clap_derive"
|
|
85
|
+
version = "4.6.1"
|
|
86
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
87
|
+
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
|
88
|
+
dependencies = [
|
|
89
|
+
"heck",
|
|
90
|
+
"proc-macro2",
|
|
91
|
+
"quote",
|
|
92
|
+
"syn",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "clap_lex"
|
|
97
|
+
version = "1.1.0"
|
|
98
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
99
|
+
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
|
100
|
+
|
|
101
|
+
[[package]]
|
|
102
|
+
name = "colorchoice"
|
|
103
|
+
version = "1.0.5"
|
|
104
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
105
|
+
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
106
|
+
|
|
107
|
+
[[package]]
|
|
108
|
+
name = "equivalent"
|
|
109
|
+
version = "1.0.2"
|
|
110
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
112
|
+
|
|
113
|
+
[[package]]
|
|
114
|
+
name = "hashbrown"
|
|
115
|
+
version = "0.17.1"
|
|
116
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
117
|
+
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
118
|
+
|
|
119
|
+
[[package]]
|
|
120
|
+
name = "heck"
|
|
121
|
+
version = "0.5.0"
|
|
122
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
123
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
124
|
+
|
|
125
|
+
[[package]]
|
|
126
|
+
name = "indexmap"
|
|
127
|
+
version = "2.14.0"
|
|
128
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
129
|
+
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
130
|
+
dependencies = [
|
|
131
|
+
"equivalent",
|
|
132
|
+
"hashbrown",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
[[package]]
|
|
136
|
+
name = "is_terminal_polyfill"
|
|
137
|
+
version = "1.70.2"
|
|
138
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
139
|
+
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
140
|
+
|
|
141
|
+
[[package]]
|
|
142
|
+
name = "memchr"
|
|
143
|
+
version = "2.8.1"
|
|
144
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
145
|
+
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
|
146
|
+
|
|
147
|
+
[[package]]
|
|
148
|
+
name = "once_cell_polyfill"
|
|
149
|
+
version = "1.70.2"
|
|
150
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
151
|
+
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
152
|
+
|
|
153
|
+
[[package]]
|
|
154
|
+
name = "proc-macro2"
|
|
155
|
+
version = "1.0.106"
|
|
156
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
157
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
158
|
+
dependencies = [
|
|
159
|
+
"unicode-ident",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
[[package]]
|
|
163
|
+
name = "quote"
|
|
164
|
+
version = "1.0.45"
|
|
165
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
166
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
167
|
+
dependencies = [
|
|
168
|
+
"proc-macro2",
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
[[package]]
|
|
172
|
+
name = "serde"
|
|
173
|
+
version = "1.0.228"
|
|
174
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
175
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
176
|
+
dependencies = [
|
|
177
|
+
"serde_core",
|
|
178
|
+
"serde_derive",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
[[package]]
|
|
182
|
+
name = "serde_core"
|
|
183
|
+
version = "1.0.228"
|
|
184
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
185
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
186
|
+
dependencies = [
|
|
187
|
+
"serde_derive",
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
[[package]]
|
|
191
|
+
name = "serde_derive"
|
|
192
|
+
version = "1.0.228"
|
|
193
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
194
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
195
|
+
dependencies = [
|
|
196
|
+
"proc-macro2",
|
|
197
|
+
"quote",
|
|
198
|
+
"syn",
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
[[package]]
|
|
202
|
+
name = "serde_spanned"
|
|
203
|
+
version = "0.6.9"
|
|
204
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
205
|
+
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
|
206
|
+
dependencies = [
|
|
207
|
+
"serde",
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
[[package]]
|
|
211
|
+
name = "strsim"
|
|
212
|
+
version = "0.11.1"
|
|
213
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
214
|
+
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
215
|
+
|
|
216
|
+
[[package]]
|
|
217
|
+
name = "syn"
|
|
218
|
+
version = "2.0.117"
|
|
219
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
220
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
221
|
+
dependencies = [
|
|
222
|
+
"proc-macro2",
|
|
223
|
+
"quote",
|
|
224
|
+
"unicode-ident",
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
[[package]]
|
|
228
|
+
name = "testing-conventions"
|
|
229
|
+
version = "0.0.2"
|
|
230
|
+
dependencies = [
|
|
231
|
+
"anyhow",
|
|
232
|
+
"clap",
|
|
233
|
+
"serde",
|
|
234
|
+
"toml",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
[[package]]
|
|
238
|
+
name = "toml"
|
|
239
|
+
version = "0.8.23"
|
|
240
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
241
|
+
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
|
242
|
+
dependencies = [
|
|
243
|
+
"serde",
|
|
244
|
+
"serde_spanned",
|
|
245
|
+
"toml_datetime",
|
|
246
|
+
"toml_edit",
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
[[package]]
|
|
250
|
+
name = "toml_datetime"
|
|
251
|
+
version = "0.6.11"
|
|
252
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
253
|
+
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|
254
|
+
dependencies = [
|
|
255
|
+
"serde",
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
[[package]]
|
|
259
|
+
name = "toml_edit"
|
|
260
|
+
version = "0.22.27"
|
|
261
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
262
|
+
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|
263
|
+
dependencies = [
|
|
264
|
+
"indexmap",
|
|
265
|
+
"serde",
|
|
266
|
+
"serde_spanned",
|
|
267
|
+
"toml_datetime",
|
|
268
|
+
"toml_write",
|
|
269
|
+
"winnow",
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
[[package]]
|
|
273
|
+
name = "toml_write"
|
|
274
|
+
version = "0.1.2"
|
|
275
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
276
|
+
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
277
|
+
|
|
278
|
+
[[package]]
|
|
279
|
+
name = "unicode-ident"
|
|
280
|
+
version = "1.0.24"
|
|
281
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
282
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
283
|
+
|
|
284
|
+
[[package]]
|
|
285
|
+
name = "utf8parse"
|
|
286
|
+
version = "0.2.2"
|
|
287
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
288
|
+
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
289
|
+
|
|
290
|
+
[[package]]
|
|
291
|
+
name = "windows-link"
|
|
292
|
+
version = "0.2.1"
|
|
293
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
294
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
295
|
+
|
|
296
|
+
[[package]]
|
|
297
|
+
name = "windows-sys"
|
|
298
|
+
version = "0.61.2"
|
|
299
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
300
|
+
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
301
|
+
dependencies = [
|
|
302
|
+
"windows-link",
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
[[package]]
|
|
306
|
+
name = "winnow"
|
|
307
|
+
version = "0.7.15"
|
|
308
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
309
|
+
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
|
310
|
+
dependencies = [
|
|
311
|
+
"memchr",
|
|
312
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "testing-conventions"
|
|
3
|
+
version = "0.0.2"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
authors = ["Kevin Scott <me@thekevinscott.com>"]
|
|
7
|
+
repository = "https://github.com/thekevinscott/testing-conventions"
|
|
8
|
+
description = "Enforce testing conventions in libraries (Python, TypeScript, and Rust)."
|
|
9
|
+
|
|
10
|
+
[lib]
|
|
11
|
+
name = "testing_conventions"
|
|
12
|
+
path = "src/lib.rs"
|
|
13
|
+
|
|
14
|
+
[[bin]]
|
|
15
|
+
name = "testing-conventions"
|
|
16
|
+
path = "src/main.rs"
|
|
17
|
+
|
|
18
|
+
[dependencies]
|
|
19
|
+
clap = { version = "4.5", features = ["derive"] }
|
|
20
|
+
anyhow = "1.0"
|
|
21
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
22
|
+
toml = "0.8"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Migrations
|
|
2
|
+
|
|
3
|
+
Upgrade notes for breaking changes. New entries go under `## Unreleased`.
|
|
4
|
+
On release, the section is renamed to `## v<OLD> → v<NEW>`.
|
|
5
|
+
|
|
6
|
+
Each entry has five sections, in order:
|
|
7
|
+
|
|
8
|
+
1. **Summary** — one paragraph: what changed and why.
|
|
9
|
+
2. **Required changes** — before/after for public API. "None" if purely additive.
|
|
10
|
+
3. **Deprecations removed** — anything previously warned about that's now gone.
|
|
11
|
+
4. **Behavior changes without code changes** — same API, different runtime behavior.
|
|
12
|
+
5. **Verification** — commands that confirm the upgrade worked, with expected output.
|
|
13
|
+
|
|
14
|
+
## Unreleased
|
|
15
|
+
|
|
16
|
+
### Summary
|
|
17
|
+
|
|
18
|
+
Adds the `config` module: a `Config` schema holding the per-language `coverage`
|
|
19
|
+
thresholds (`[python]` / `[typescript]` / `[rust]`), plus `load_config()`, which
|
|
20
|
+
reads one TOML file into it and validates the config itself (the self-guard) —
|
|
21
|
+
unknown keys and malformed TOML are rejected rather than silently accepted.
|
|
22
|
+
Purely additive; nothing consumes the parsed config yet.
|
|
23
|
+
|
|
24
|
+
### Required changes
|
|
25
|
+
|
|
26
|
+
None. New, additive API: `testing_conventions::config::{Config, load_config}`.
|
|
27
|
+
|
|
28
|
+
### Deprecations removed
|
|
29
|
+
|
|
30
|
+
None.
|
|
31
|
+
|
|
32
|
+
### Behavior changes without code changes
|
|
33
|
+
|
|
34
|
+
None.
|
|
35
|
+
|
|
36
|
+
### Verification
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
cd packages/rust && cargo test --test config_loader
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Expected: the loader's integration tests pass — the canonical config loads into
|
|
43
|
+
memory, and unknown-key, malformed, and missing-file configs are rejected.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//! The testing-conventions config schema and loader.
|
|
2
|
+
//!
|
|
3
|
+
//! One config file is read into the in-memory [`Config`] below. The loader
|
|
4
|
+
//! parses *and* validates the config itself (the "self-guard" from issue #12):
|
|
5
|
+
//! a malformed or unknown-key config is an error, never a silently-accepted
|
|
6
|
+
//! default.
|
|
7
|
+
//!
|
|
8
|
+
//! Nothing consumes [`Config`] yet — this module only turns the file on disk
|
|
9
|
+
//! into the in-memory structure.
|
|
10
|
+
|
|
11
|
+
use std::path::Path;
|
|
12
|
+
|
|
13
|
+
use anyhow::{Context, Result};
|
|
14
|
+
use serde::Deserialize;
|
|
15
|
+
|
|
16
|
+
/// A fully-parsed testing-conventions config file.
|
|
17
|
+
///
|
|
18
|
+
/// Holds the per-language coverage thresholds — the `[python]` / `[typescript]`
|
|
19
|
+
/// / `[rust]` tables from the README's "Configuration" section. Each table is
|
|
20
|
+
/// optional so a repo can configure only the languages it ships. Test locations
|
|
21
|
+
/// follow convention, not config, so there are no location keys here.
|
|
22
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
23
|
+
#[serde(deny_unknown_fields)]
|
|
24
|
+
pub struct Config {
|
|
25
|
+
pub python: Option<PythonConfig>,
|
|
26
|
+
pub typescript: Option<TypeScriptConfig>,
|
|
27
|
+
pub rust: Option<RustConfig>,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// The `[python]` table.
|
|
31
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
32
|
+
#[serde(deny_unknown_fields)]
|
|
33
|
+
pub struct PythonConfig {
|
|
34
|
+
pub coverage: PythonCoverage,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// The `[typescript]` table.
|
|
38
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
39
|
+
#[serde(deny_unknown_fields)]
|
|
40
|
+
pub struct TypeScriptConfig {
|
|
41
|
+
pub coverage: TypeScriptCoverage,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// The `[rust]` table.
|
|
45
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
46
|
+
#[serde(deny_unknown_fields)]
|
|
47
|
+
pub struct RustConfig {
|
|
48
|
+
pub coverage: RustCoverage,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// `[python].coverage`.
|
|
52
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
53
|
+
#[serde(deny_unknown_fields)]
|
|
54
|
+
pub struct PythonCoverage {
|
|
55
|
+
pub branch: bool,
|
|
56
|
+
pub fail_under: u8,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// `[typescript].coverage`.
|
|
60
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
61
|
+
#[serde(deny_unknown_fields)]
|
|
62
|
+
pub struct TypeScriptCoverage {
|
|
63
|
+
pub lines: u8,
|
|
64
|
+
pub branches: u8,
|
|
65
|
+
pub functions: u8,
|
|
66
|
+
pub statements: u8,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// `[rust].coverage`. Branch coverage is still experimental, so only
|
|
70
|
+
/// regions/lines are configurable.
|
|
71
|
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
72
|
+
#[serde(deny_unknown_fields)]
|
|
73
|
+
pub struct RustCoverage {
|
|
74
|
+
pub regions: u8,
|
|
75
|
+
pub lines: u8,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Read one config file at `path` into a [`Config`], validating it on the way.
|
|
79
|
+
///
|
|
80
|
+
/// The validation is the config's self-guard: `serde`'s `deny_unknown_fields`
|
|
81
|
+
/// rejects keys that aren't part of the schema, missing required keys and
|
|
82
|
+
/// wrong-typed values are type errors, and malformed TOML fails to parse. Any
|
|
83
|
+
/// of these surfaces as an `Err` rather than a silently-accepted default.
|
|
84
|
+
pub fn load_config(path: impl AsRef<Path>) -> Result<Config> {
|
|
85
|
+
let path = path.as_ref();
|
|
86
|
+
let contents = std::fs::read_to_string(path)
|
|
87
|
+
.with_context(|| format!("reading config file `{}`", path.display()))?;
|
|
88
|
+
toml::from_str(&contents).with_context(|| format!("parsing config file `{}`", path.display()))
|
|
89
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
pub mod config;
|
|
2
|
+
|
|
3
|
+
use clap::{Parser, Subcommand};
|
|
4
|
+
|
|
5
|
+
#[derive(Parser, Debug)]
|
|
6
|
+
#[command(
|
|
7
|
+
name = "testing-conventions",
|
|
8
|
+
version,
|
|
9
|
+
about = "Enforce testing conventions in libraries (Python, TypeScript, and Rust).",
|
|
10
|
+
long_about = None,
|
|
11
|
+
)]
|
|
12
|
+
pub struct Cli {
|
|
13
|
+
#[command(subcommand)]
|
|
14
|
+
command: Option<Command>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Subcommand, Debug)]
|
|
18
|
+
enum Command {
|
|
19
|
+
/// Check the repository against its testing-conventions config.
|
|
20
|
+
Check,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn run<I, T>(args: I) -> anyhow::Result<i32>
|
|
24
|
+
where
|
|
25
|
+
I: IntoIterator<Item = T>,
|
|
26
|
+
T: Into<std::ffi::OsString> + Clone,
|
|
27
|
+
{
|
|
28
|
+
let cli = Cli::try_parse_from(args)?;
|
|
29
|
+
match cli.command {
|
|
30
|
+
// Rules aren't implemented yet; the scaffold just proves the wiring.
|
|
31
|
+
Some(Command::Check) | None => Ok(0),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[cfg(test)]
|
|
36
|
+
mod tests {
|
|
37
|
+
use super::*;
|
|
38
|
+
|
|
39
|
+
#[test]
|
|
40
|
+
fn no_args_returns_ok_zero() {
|
|
41
|
+
assert_eq!(run(["testing-conventions"]).unwrap(), 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[test]
|
|
45
|
+
fn check_returns_ok_zero() {
|
|
46
|
+
assert_eq!(run(["testing-conventions", "check"]).unwrap(), 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[test]
|
|
50
|
+
fn unknown_flag_errors() {
|
|
51
|
+
assert!(run(["testing-conventions", "--bogus"]).is_err());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn help_flag_returns_clap_display_help() {
|
|
56
|
+
let err = run(["testing-conventions", "--help"]).expect_err("--help should bubble");
|
|
57
|
+
let clap_err = err
|
|
58
|
+
.downcast_ref::<clap::Error>()
|
|
59
|
+
.expect("error should be a clap::Error");
|
|
60
|
+
assert_eq!(clap_err.kind(), clap::error::ErrorKind::DisplayHelp);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[test]
|
|
64
|
+
fn version_flag_returns_clap_display_version() {
|
|
65
|
+
let err = run(["testing-conventions", "--version"]).expect_err("--version should bubble");
|
|
66
|
+
let clap_err = err
|
|
67
|
+
.downcast_ref::<clap::Error>()
|
|
68
|
+
.expect("error should be a clap::Error");
|
|
69
|
+
assert_eq!(clap_err.kind(), clap::error::ErrorKind::DisplayVersion);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
use std::process::ExitCode;
|
|
2
|
+
|
|
3
|
+
fn main() -> ExitCode {
|
|
4
|
+
match testing_conventions::run(std::env::args_os()) {
|
|
5
|
+
Ok(code) => ExitCode::from(code as u8),
|
|
6
|
+
Err(err) => {
|
|
7
|
+
if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
|
|
8
|
+
clap_err.exit();
|
|
9
|
+
}
|
|
10
|
+
eprintln!("error: {err}");
|
|
11
|
+
ExitCode::from(1)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//! Integration tests for the config schema + loader (issue #12).
|
|
2
|
+
//!
|
|
3
|
+
//! These pin the contract from the README's "Configuration" section: one config
|
|
4
|
+
//! file is read into the in-memory `Config`, and the self-guard rejects a config
|
|
5
|
+
//! that fails its own validation (unknown keys, malformed TOML) rather than
|
|
6
|
+
//! silently accepting it.
|
|
7
|
+
//!
|
|
8
|
+
//! Per the #3 guardrail, the loader ships a clean fixture (`valid.toml`, must
|
|
9
|
+
//! load) and red fixtures (`unknown_key.toml` / `malformed.toml`, must fail).
|
|
10
|
+
|
|
11
|
+
use std::path::PathBuf;
|
|
12
|
+
|
|
13
|
+
use testing_conventions::config::{
|
|
14
|
+
load_config, Config, PythonConfig, PythonCoverage, RustConfig, RustCoverage, TypeScriptConfig,
|
|
15
|
+
TypeScriptCoverage,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/// Absolute path to a file under `tests/fixtures/`.
|
|
19
|
+
fn fixture(name: &str) -> PathBuf {
|
|
20
|
+
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
21
|
+
.join("tests/fixtures")
|
|
22
|
+
.join(name)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// The in-memory shape we expect `valid.toml` to parse into.
|
|
26
|
+
fn expected_valid() -> Config {
|
|
27
|
+
Config {
|
|
28
|
+
python: Some(PythonConfig {
|
|
29
|
+
coverage: PythonCoverage {
|
|
30
|
+
branch: true,
|
|
31
|
+
fail_under: 100,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
typescript: Some(TypeScriptConfig {
|
|
35
|
+
coverage: TypeScriptCoverage {
|
|
36
|
+
lines: 100,
|
|
37
|
+
branches: 100,
|
|
38
|
+
functions: 100,
|
|
39
|
+
statements: 100,
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
rust: Some(RustConfig {
|
|
43
|
+
coverage: RustCoverage {
|
|
44
|
+
regions: 100,
|
|
45
|
+
lines: 100,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[test]
|
|
52
|
+
fn loads_the_canonical_config_into_memory() {
|
|
53
|
+
let config = load_config(fixture("valid.toml")).expect("the canonical config should load");
|
|
54
|
+
assert_eq!(config, expected_valid());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#[test]
|
|
58
|
+
fn rejects_unknown_keys_self_guard() {
|
|
59
|
+
let result = load_config(fixture("unknown_key.toml"));
|
|
60
|
+
assert!(
|
|
61
|
+
result.is_err(),
|
|
62
|
+
"an unknown config key must be rejected (self-guard), got: {result:?}"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn rejects_malformed_toml() {
|
|
68
|
+
let result = load_config(fixture("malformed.toml"));
|
|
69
|
+
assert!(
|
|
70
|
+
result.is_err(),
|
|
71
|
+
"malformed TOML must be rejected, got: {result:?}"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn errors_on_a_missing_file() {
|
|
77
|
+
let result = load_config(fixture("does_not_exist.toml"));
|
|
78
|
+
assert!(
|
|
79
|
+
result.is_err(),
|
|
80
|
+
"a missing config file must be an error, got: {result:?}"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Red fixture: a valid coverage config plus one key the schema doesn't know
|
|
2
|
+
# (`unknown`). The loader's self-guard (serde `deny_unknown_fields`) must reject
|
|
3
|
+
# it — keeping the rest valid means this fixture fails *only* on the bad key.
|
|
4
|
+
[python]
|
|
5
|
+
coverage = { branch = true, fail_under = 100 }
|
|
6
|
+
unknown = true
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Clean fixture: per-language coverage thresholds. Must load (issue #12).
|
|
2
|
+
[python]
|
|
3
|
+
coverage = { branch = true, fail_under = 100 }
|
|
4
|
+
|
|
5
|
+
[typescript]
|
|
6
|
+
coverage = { lines = 100, branches = 100, functions = 100, statements = 100 }
|
|
7
|
+
|
|
8
|
+
[rust]
|
|
9
|
+
coverage = { regions = 100, lines = 100 }
|