Skip to content

Commit a560436

Browse files
committed
Initial commit
0 parents  commit a560436

File tree

14 files changed

+578
-0
lines changed

14 files changed

+578
-0
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[*.go]
2+
tab_width = 4
3+
max_line_length = 80

.github/workflows/main.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: main
2+
on: [push]
3+
jobs:
4+
op:
5+
runs-on: ${{ matrix.os }}
6+
strategy:
7+
matrix:
8+
os: [ubuntu-latest, macos-latest, windows-latest]
9+
go: [stable, oldstable]
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-go@v5
13+
with:
14+
go-version: ${{ matrix.go }}
15+
check-latest: true
16+
- run: go install lesiw.io/op@latest
17+
- run: op

.golangci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
linters:
2+
enable:
3+
- errcheck
4+
- errname
5+
- gocheckcompilerdirectives
6+
- gocyclo
7+
- godot
8+
- lll
9+
- makezero
10+
- revive
11+
- unparam
12+
- unused
13+
14+
linters-settings:
15+
gocyclo:
16+
min-complexity: 15
17+
lll:
18+
tab-width: 4
19+
line-length: 79
20+
21+
issues:
22+
exclude-rules:
23+
- linters:
24+
- errcheck
25+
source: "defer "
26+
exclude-use-default: false
27+
exclude:
28+
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked
29+
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
30+
- (possible misuse of unsafe.Pointer|should have signature)
31+
- ineffective break statement. Did you mean to break out of the outer loop
32+
- Use of unsafe calls should be audited
33+
- Subprocess launch(ed with variable|ing should be audited)
34+
- G104
35+
- (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)
36+
- Potential file inclusion via variable

.ops/.uuid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1840c247-60c6-473b-8196-7b3a319f2324

.ops/go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module ops.localhost
2+
3+
go 1.24.0
4+
5+
require (
6+
labs.lesiw.io/ops v0.0.0-20250220031357-f371eedb0d12
7+
lesiw.io/ops v0.14.0
8+
)
9+
10+
require (
11+
golang.org/x/crypto v0.33.0 // indirect
12+
golang.org/x/sync v0.11.0 // indirect
13+
lesiw.io/clerk v0.2.0 // indirect
14+
lesiw.io/cmdio v0.9.0 // indirect
15+
lesiw.io/cmdio/x/busybox v0.1.0 // indirect
16+
lesiw.io/defers v0.9.0 // indirect
17+
lesiw.io/flag v0.7.0 // indirect
18+
lesiw.io/prefix v0.1.0 // indirect
19+
)

.ops/go.sum

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
4+
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
5+
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
6+
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
7+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
8+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
9+
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
10+
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
11+
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
12+
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
13+
labs.lesiw.io/ops v0.0.0-20250220031357-f371eedb0d12 h1:LTlhmr7P3ND6tyiJtOo5acNRrv70IYFdDe0hdVq0Opk=
14+
labs.lesiw.io/ops v0.0.0-20250220031357-f371eedb0d12/go.mod h1:C9Jk4AQOr5M0NGk2oTkNJwGdX7mISjG+tebn0n3SyII=
15+
lesiw.io/clerk v0.2.0 h1:Rcq19dHx9TRk4Rkpm9Sw/KGySUxrNmPhrcmU2EQhBYo=
16+
lesiw.io/clerk v0.2.0/go.mod h1:WMyvgTe+3Eob36b6KX86MOaxaeNMvv/HZTSsf2Dedhg=
17+
lesiw.io/cmdio v0.9.0 h1:m4soyeXn9Xuelt6UHr4+piFLheb/L4lveJJ8p6PguGE=
18+
lesiw.io/cmdio v0.9.0/go.mod h1:RRxYqFsLgx3JpElt244AGmRdeFGnc/6fbh3fFgcNvnk=
19+
lesiw.io/cmdio/x/busybox v0.1.0 h1:e1G1JJttIcwpGuUki7oLQTx7Ve6BWExe2Cs4aw0kExM=
20+
lesiw.io/cmdio/x/busybox v0.1.0/go.mod h1:PkwZVzE2tTrNI4BNOgiz595SwwF8BAbvI9LSz1P5GJY=
21+
lesiw.io/defers v0.9.0 h1:Sg7RYbhxfHhXMHclO65MJ4oRbyhfSBSeHQw4YjLr6n0=
22+
lesiw.io/defers v0.9.0/go.mod h1:AP09yGFHxL5vmTVJxkPL33N1hWI4OzHwTEOzilbDZU4=
23+
lesiw.io/flag v0.7.0 h1:+8rTdoplDMBhOSKok5eKP6ZuLLPTodkDABRY7jfX5JU=
24+
lesiw.io/flag v0.7.0/go.mod h1:bJx6Hir8MAXkNiO6BbrvhwZuaJF4rQWthQ7pc1DlWZY=
25+
lesiw.io/ops v0.14.0 h1:WTdNxlqL2HjDgpDd5VqoaQwKOqvu1CeR1Wi1wk6wJ0E=
26+
lesiw.io/ops v0.14.0/go.mod h1:onq4+252Tz6+DzGMtUiibOm7+KJW+ty8v5OBJ91Yb7M=
27+
lesiw.io/prefix v0.1.0 h1:2Ors12avAiADgMbsQHh27wQmoSI+4aZSW2uTBdDmIZg=
28+
lesiw.io/prefix v0.1.0/go.mod h1:yrUaJpvikavNodwcL64crLnSf3t1NWeNi/vNu5hUi2Y=

.ops/ops.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// op is the command line for project operations.
2+
package main
3+
4+
import (
5+
"os"
6+
7+
"labs.lesiw.io/ops/goapp"
8+
"lesiw.io/ops"
9+
)
10+
11+
// Ops is the set of operations for this project.
12+
type Ops struct{ goapp.Ops }
13+
14+
func main() {
15+
goapp.Name = "testdetect"
16+
if len(os.Args) < 2 {
17+
os.Args = append(os.Args, "build")
18+
}
19+
ops.Handle(Ops{})
20+
}

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2025 Chris Lesiw
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# testdetect
2+
3+
Despite [many](https://github.com/golang/go/issues/12120),
4+
[many](https://github.com/golang/go/issues/14668),
5+
[many](https://github.com/golang/go/issues/21360),
6+
[many](https://github.com/golang/go/issues/52600),
7+
[many](https://github.com/golang/go/issues/60737),
8+
[many](https://github.com/golang/go/issues/60772),
9+
[many](https://github.com/golang/go/issues/64356)
10+
requests and proposals for a compile-time constraint to strip out test-time
11+
specific code, Go still does not have a `test` build tag.
12+
[`testing.Testing()`](https://pkg.go.dev/testing#Testing) was added in Go 1.21
13+
but is sadly not constant, meaning any code gated behind it will still be
14+
present in a release binary.
15+
16+
`testdetect` generates a package-local `testingDetector` type with a single
17+
method, `Testing()`. While not a true constant, this method is "constant
18+
enough" that most Go compilers will optimize test related branches out of the
19+
finished binary. In this way, it's the closest thing to an `#ifdef TEST` as we
20+
can get.
21+
22+
## Usage
23+
24+
Run this in your Go package's directory.
25+
26+
```sh
27+
go run lesiw.io/testdetect@latest
28+
```
29+
30+
Alternatively, include it in your Go code as a `go generate` directive.
31+
32+
```go
33+
//go:generate go run lesiw.io/testdetect@latest
34+
```
35+
36+
This produces two files, `testing_detector.go` and `testing_detector_test.go`.
37+
38+
Write your test-specific code behind a `(testingDetector).Testing()` check.
39+
40+
```go file=main.go
41+
package main
42+
43+
var t testingDetector
44+
45+
func main() {
46+
if t.Testing() {
47+
println("t.Testing()=true")
48+
} else {
49+
println("t.Testing()=false")
50+
}
51+
println("Hello world!")
52+
}
53+
```
54+
55+
## Caveats and details
56+
57+
As of February 2025, checking for `Testing()` in this way correctly strips
58+
test-related branches from Go programs compiled by `gc` (the primary Go
59+
implementation) and `tinygo`. It does not work for `gccgo`.
60+
61+
Technically, this is reliant on implementation details of each of these
62+
compilers, which are not defined in the Go specification and are subject to
63+
change. That said, I find it unlikely that dead code elimination will regress
64+
to the point where these branches are no longer optimized out, and even when
65+
building binaries using versions of the Go toolchain from years ago,
66+
removal of the `if t.Testing() == true` branches has proven consistent.
67+
68+
This generator generates a number of superfluous lines to avoid contributing
69+
negatively to code coverage or tripping up popular linting tools. Since the
70+
entire point of this package is to provide a testing tool that hopefully helps
71+
you improve your own code coverage, generating additional uncovered lines is
72+
considered a bug.
73+
74+
It is theoretically possible to tamper with the value of `t.Testing()`. To
75+
validate that this does not happen, an `init()` function has been added to
76+
`testing_detector.go `that checks to ensure the value of `t.Testing()` is
77+
always equal to `testing.Testing()`, whose value is set at compile time. In
78+
the unlikely event someone were to add the contents of the
79+
`testing_detector_test.go` file into a large codebase, the program would detect
80+
the discrepancy and panic on initialization.
81+
82+
The actual mechanism behind `testingDetector`'s differing behavior between
83+
test and non-test binaries is well-defined in the
84+
[Go spec](https://go.dev/ref/spec). Specifically, it (ab)uses
85+
[selector rules](https://go.dev/ref/spec#Selectors), adding a new method to
86+
`testingDetector` type that is only present in the `_test.go` file. So while
87+
implementations of dead code elimination may differ from compiler to compiler,
88+
this code is perfectly valid by Go spec rules and will never fail to compile.
89+
90+
## A hypothetical better future
91+
92+
I've built this in the hopes that it will someday be retired.
93+
94+
All of this package's complexity could be reduced by treating `_test.go` files
95+
the same as any other build constraint and exposing a `test` build tag.
96+
97+
```go filename=undertest_false.go
98+
//go:build !test
99+
// +build !test
100+
package main
101+
102+
const undertest = false
103+
```
104+
105+
```go filename=undertest_true.go
106+
//go:build test
107+
// +build test
108+
package main
109+
110+
const undertest = true
111+
```
112+
113+
Being a constant declaration, this does not harm code coverage metrics and is
114+
impossible to tamper with: a constant can only be declared once, so declaring
115+
it a second time elsewhere in the code would be a compiler error.
116+
117+
I am personally of the opinion that this is simpler, more understandable, less
118+
surprising, and a simple `if false == true` check is highly likely to be
119+
optimized out by any current or future Go compiler.

clerk.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.editorconfig 3632ddbf531e998e5990a3d4e88b547fea86dd00
2+
.github/workflows/main.yml 62efcdd9a8f00f489693e4f0f916429822dc311f
3+
.golangci.yml 024a396bbe13b652e1c0c871bacf066672a5b18a

0 commit comments

Comments
 (0)