Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Port SemVer Go

A Go port of python-semver (commit: 6adf8765f6e21910f1f0c13151ce84f32f8d431d, tag 3.0.4), built for PORT MORTEM 2026.

Track: Open Pair (Python → Go).

What's ported

  • Parse / String — full SemVer 2.0.0 grammar
  • Compare — precedence rules per semver.org section 11
  • BumpMajor / BumpMinor / BumpPatch
  • IsValid
  • next_version

What's NOT ported (scope cut, documented)

  • match() — range syntax like >=1.0.0 <2.0.0. It's effectively its own small parser, meaningfully more scope than everything else combined. Cut deliberately to keep the port small enough to fully prove correct in the time available, rather than bigger with an unfuzzed corner.
  • replace(), to_dict(), to_tuple(), __hash__, __getitem__ — Python ergonomics (dict-style access, hashing for sets, tuple conversion) that don't map to idiomatic Go. Compare() already covers what the dunder methods are doing under the hood.
  • CLI wrapper (cli.py) — not core library logic, out of scope for a port that's about proving behavioral equivalence.

See DECISIONS.md for the full reasoning.

Build

go build -o semverport .
go test ./...

Differential fuzzing

pip install semver
go build -o semverport .
cd fuzz && python3 differential_fuzz.py --cases 5000

Testing & Equivalence

  • Unit tests (go test ./...), all passing
  • Differential fuzzing against the real python-semver 3.0.4 (pinned tag, matching the hashed test suite): parse, compare, bump_major/minor/patch, bump_prerelease/bump_build, finalize_version, next_version — all exercised across seeds 0, -1, 5, 2, 7, 99 (~30,000 total generated cases(6 seeds x 5k cases)) with 0 mismatches on the final build
  • Two real issues surfaced during fuzzing and fixed (see DECISIONS.md): a JSON serialization bug that dropped legitimate zero values, and a zero-padding bug in bump_build/bump_prerelease that didn't preserve digit width on increment (e.g. V00 → V1 instead of V01)
  • Original test suite hash + source commit recorded below, untouched

Original test suite hash

Pinned to python-semver's published release tag 3.0.4 (commit 6adf8765f6e21910f1f0c13151ce84f32f8d431d) — matching the pip package version (3.0.4) actually used for differential fuzzing, so the provenance hash and the fuzz ground-truth are the same version.

(Earlier in development this was pinned to main, ahead of the last published release, which caused a real mismatch when writing next_version — caught and corrected here. See DECISIONS.md.)

Test directory hash: ad39f5355c3382c44dc4f9dfd47bf247e73fb85b4eb87f3188b946c036f74a8e

Reproduce:

git clone https://github.com/python-semver/python-semver.git original
cd original && git checkout 3.0.4 && cd ..
find original/tests -type f | sort | xargs sha256sum | sha256sum

Benchmark

Measured on an Intel Core 2 Duo E7500 @ 2.93GHz (Linux Mint XFCE, 2GB RAM).

Operation Go (this port) Python (original) Speedup
Parse 2,668 ns/op 8,188 ns/op ~3.1x
Compare 7.5 ns/op 2,725 ns/op ~363x

Reprodue:

go test ./semver -bench=. -benchtime=1s
python3 -c "
import timeit, semver
print(timeit.timeit(lambda: semver.Version.parse('1.2.3-alpha.1+build.5'), number=100000)/1000001e9, 'ns/op parse')
v1 = semver.Version.parse('1.2.3-alpha.1'); v2 = semver.Version.parse('1.2.4-beta.2')
print(timeit.timeit(lambda: v1.compare(v2), number=100000)/1000001e9, 'ns/op compare')
"

About

A Go port of python-semver, built for PORT MORTEM 2026. Proven equivalent via ~30k differential fuzz cases against the original, 0 mismatches.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages