Compare commits
1 Commits
f84378c426
...
d5ed473e31
| Author | SHA1 | Date | |
|---|---|---|---|
| d5ed473e31 |
12
Makefile
12
Makefile
@@ -23,13 +23,19 @@ lint:
|
|||||||
|
|
||||||
# Refresh the vendored dataset from the BlogsURL constant in hnblogs.go, which
|
# Refresh the vendored dataset from the BlogsURL constant in hnblogs.go, which
|
||||||
# is the single source of truth for the upstream location. The download lands
|
# is the single source of truth for the upstream location. The download lands
|
||||||
# on a temporary file and only replaces blogs.json once it is complete, so an
|
# on a temporary file and only replaces blogs.json once it has been checked to
|
||||||
# interrupted fetch cannot leave a truncated dataset committed.
|
# be a non-empty JSON array of blog entries, so neither an interrupted transfer
|
||||||
|
# nor a complete-but-wrong response (an error page, a redirect landing page)
|
||||||
|
# can overwrite the good dataset.
|
||||||
update-data:
|
update-data:
|
||||||
|
@command -v jq >/dev/null || { echo "update-data requires jq" >&2; exit 1; }
|
||||||
@url=$$(sed -n 's/^const BlogsURL = "\(.*\)"$$/\1/p' hnblogs.go); \
|
@url=$$(sed -n 's/^const BlogsURL = "\(.*\)"$$/\1/p' hnblogs.go); \
|
||||||
test -n "$$url" || { echo "could not parse BlogsURL from hnblogs.go" >&2; exit 1; }; \
|
test -n "$$url" || { echo "could not parse BlogsURL from hnblogs.go" >&2; exit 1; }; \
|
||||||
echo "fetching $$url"; \
|
echo "fetching $$url"; \
|
||||||
curl -fsSL "$$url" -o blogs.json.tmp
|
curl -fsSL "$$url" -o blogs.json.tmp
|
||||||
@test -s blogs.json.tmp || { echo "downloaded dataset is empty" >&2; rm -f blogs.json.tmp; exit 1; }
|
@jq -e 'type == "array" and length > 0 and all(.[]; type == "object" and has("url"))' \
|
||||||
|
blogs.json.tmp >/dev/null 2>&1 \
|
||||||
|
|| { echo "download is not a non-empty JSON array of blog entries; blogs.json left unchanged" >&2; \
|
||||||
|
rm -f blogs.json.tmp; exit 1; }
|
||||||
mv blogs.json.tmp blogs.json
|
mv blogs.json.tmp blogs.json
|
||||||
$(MAKE) test
|
$(MAKE) test
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -24,6 +24,17 @@ The trade-off is that the dataset is a build-time artifact: it is roughly 8 MB
|
|||||||
of JSON, it lands in every binary that links the package, and it is only as
|
of JSON, it lands in every binary that links the package, and it is only as
|
||||||
fresh as the last commit that refreshed it.
|
fresh as the last commit that refreshed it.
|
||||||
|
|
||||||
|
### Where blogs.json came from
|
||||||
|
|
||||||
|
`blogs.json` is not this project's work. It is an unmodified copy of
|
||||||
|
|
||||||
|
<https://raw.githubusercontent.com/surprisetalk/blogs.hn/main/blogs.json>
|
||||||
|
|
||||||
|
from the [surprisetalk/blogs.hn](https://github.com/surprisetalk/blogs.hn)
|
||||||
|
repository, which publishes no licence. This repository's `LICENSE` covers the
|
||||||
|
code here and does not extend to `blogs.json`, and a binary that links this
|
||||||
|
package redistributes that file too.
|
||||||
|
|
||||||
## Refreshing the dataset
|
## Refreshing the dataset
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -31,10 +42,12 @@ make update-data
|
|||||||
```
|
```
|
||||||
|
|
||||||
That target reads the upstream location from the `BlogsURL` constant in
|
That target reads the upstream location from the `BlogsURL` constant in
|
||||||
`hnblogs.go` — the single source of truth — downloads to a temporary file,
|
`hnblogs.go` — the single source of truth — and downloads it to a temporary
|
||||||
replaces `blogs.json` only once the download completes, and then runs the test
|
file. It replaces `blogs.json` only once that file parses as a non-empty JSON
|
||||||
suite against the new data. Commit the resulting `blogs.json` to publish the
|
array of blog entries, so a response that arrives complete but is not the
|
||||||
update.
|
dataset leaves the vendored copy untouched. It then runs the test suite
|
||||||
|
against the new data. Requires `curl` and `jq`. Commit the resulting
|
||||||
|
`blogs.json` to publish the update.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -44,4 +57,4 @@ make lint
|
|||||||
make docker
|
make docker
|
||||||
```
|
```
|
||||||
|
|
||||||
`make docker` runs lint and tests in containers, matching CI.
|
`make docker` runs lint and tests in containers.
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ import (
|
|||||||
// it documents where "make update-data" pulls the vendored copy from.
|
// it documents where "make update-data" pulls the vendored copy from.
|
||||||
const BlogsURL = "https://raw.githubusercontent.com/surprisetalk/blogs.hn/main/blogs.json"
|
const BlogsURL = "https://raw.githubusercontent.com/surprisetalk/blogs.hn/main/blogs.json"
|
||||||
|
|
||||||
// blogsJSON is the vendored dataset, refreshed by "make update-data".
|
// blogsJSON is the vendored dataset, refreshed by "make update-data". It is an
|
||||||
|
// unmodified copy of a third party's file, not this project's work; see "Where
|
||||||
|
// blogs.json came from" in README.md.
|
||||||
//
|
//
|
||||||
//go:embed blogs.json
|
//go:embed blogs.json
|
||||||
var blogsJSON []byte
|
var blogsJSON []byte
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package hnblogs
|
package hnblogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"go/build"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -39,17 +40,25 @@ func TestEmbeddedDataIsUsable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestNoRuntimeNetworkImports is the regression guard for the reason this data
|
// TestNoRuntimeNetworkImports is the regression guard for the reason this data
|
||||||
// is embedded: the package must not reach the network on any code path. A
|
// is embedded: the package must not reach the network on any code path. It
|
||||||
// transport dependency reintroduced in non-test code fails here.
|
// walks the whole dependency graph of the non-test build rather than the direct
|
||||||
|
// import list, so a net/* package reached through an intermediate import fails
|
||||||
|
// here too. Test-only imports are outside that graph, which is why this test
|
||||||
|
// may use os/exec itself.
|
||||||
func TestNoRuntimeNetworkImports(t *testing.T) {
|
func TestNoRuntimeNetworkImports(t *testing.T) {
|
||||||
pkg, err := build.ImportDir(".", 0)
|
cmd := exec.Command("go", "list", "-deps", ".")
|
||||||
|
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to inspect package imports: %v", err)
|
t.Fatalf("Failed to list package dependencies: %v\n%s", err, stderr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, imported := range pkg.Imports {
|
for _, dep := range strings.Fields(string(out)) {
|
||||||
if imported == "net" || strings.HasPrefix(imported, "net/") {
|
if dep == "net" || strings.HasPrefix(dep, "net/") {
|
||||||
t.Fatalf("Package must not perform network I/O, but imports %q", imported)
|
t.Fatalf("Package must not perform network I/O, but depends on %q", dep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user