feat: modernize API, fix critical bugs, and add test suite#1
Merged
Conversation
## Bug fixes
- Fix Darwin deadlock: extract stopLocked() (no mutex), Stop() waits outside lock
- Fix sync.Once preventing restart: replaced with capturing bool guard
- Fix goroutine leak in coremidi NewDestination: close writeFd on creation failure
- Fix Windows syscall error handling: use r1 != 0 only, ignore spurious GetLastError
- Replace errors.New(fmt.Sprintf(...)) with fmt.Errorf throughout coremidi
## API modernization
- Field: interface → plain struct {Key string; Value any} with standalone constructors
(contracts.IntField, contracts.StringField, contracts.ErrField, etc.)
- StartCapture: (chan MIDI) → (ctx context.Context) (<-chan MIDI, error)
Client now creates and owns the channel; cancelled context triggers Stop()+close
- Add WithChannelBufferSize(n) option (default 100)
- Add WithLogDestination(dest, filePath...) option for console/file log routing
- Fix LogLevel default bug: add logLevelSet bool + LogLevelIsSet() method
- Fix LogLevel ordering: Debug < Info < Warn < Error < Fatal
- Extract IsCommandAllowed to contracts.IsCommandAllowed (removes duplication)
- Unexport DummyMIDIClient → dummyMIDIClient in darwin and windows stubs
- Add sentinel error variables in mididarwin and midiwindows
## Code quality
- Replace uber-zap with lightweight stdLogger using encoding/json fields
- Add NewLoggerWithWriter(w io.Writer) for testability
- NewLogger() / NewZapLogger() / NewStandardLogger() all available
## Tests (37 new test cases, all passing)
- sdk/contracts/filter_test.go — IsCommandAllowed (5 cases)
- sdk/contracts/mock.go + mock_test.go — MockMIDIClient for consumers (3 cases)
- sdk/midi/options_setup_test.go — applyDefaultOptions (8 cases)
- internal/logger/logger_wrapper_test.go — level filtering, fields, output (9 cases)
## Docs
- Add .github/copilot-instructions.md
- Add CLAUDE.md with build/test commands and architecture overview
- Update README with new API usage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- vet job runs go vet on ubuntu first - test matrix: macos-latest (CGo=1, CoreMIDI), windows-latest (CGo=0, syscalls), ubuntu-latest (CGo=0, dummy stubs) - uses go-version-file: go.mod to track version automatically - fail-fast: false so all three platforms always run independently Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Windows: CGO_ENABLED=1 (gcc pre-installed on runners) so -race works - Linux: CGO_ENABLED=0 (no C toolchain needed for dummy stubs), race='' to skip -race - Add FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true env to silence deprecation warnings for actions/checkout and actions/setup-go Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eliminates Node.js 20 deprecation warnings. Both v6 releases natively target Node.js 24. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete modernization of the MIDI library: critical bug fixes, API redesign, and a new test suite (37 tests).
Bug Fixes
Darwin deadlock (critical)
StartCaptureheldmu.Lock()and calledStop(), which tried to re-acquire the same lock → deadlock. Fixed withstopLocked()pattern:Stop()acquires lock → callsstopLocked()→ releases lock → callswg.Wait()outside the lock.sync.Once preventing restart
stopOnce sync.Oncemeant after the firstStop(), the client could never stop again after a restart. Replaced with a simplecapturing boolguard.Goroutine leak in CoreMIDI NewDestination
The
processIncomingPacketgoroutine was started beforeMIDIDestinationCreate. On failure,writeFdwas never closed → goroutine blocked forever. Fixed: closewriteFdon creation failure.Windows syscall error handling
windows.NewLazySystemDLLproc.Call()always returns a non-nilerr(it is Win32GetLastError, not a Go error). Fixed: only user1 != 0as the error indicator.fmt.Errorf throughout coremidi
All
errors.New(fmt.Sprintf(...))replaced withfmt.Errorf(...).API Changes (breaking)
Field→ plain structFieldwas a confusing interface that doubled as both a type and a builder. Now it's a plainstruct { Key string; Value any }with standalone constructors:StartCapturesignatureThe client now creates and owns the channel. Cancelling the context triggers
Stop()and closes the channel automatically.New options
WithChannelBufferSize(n int)— configure event channel buffer (default: 100)WithLogDestination(dest, filePath...)— route logs to console or fileLogLevel fix
InfoLevel = iota = 0caused the default check to be ambiguous. AddedlogLevelSet bool+LogLevelIsSet(). LogLevel ordering corrected:Debug < Info < Warn < Error < Fatal.contracts.IsCommandAllowedExtracted from identical duplications in both darwin and windows clients. Handles nil filter internally.
dummyMIDIClientunexportedThe cross-platform stub was exported unnecessarily.
Logger Rewrite
Replaced the uber-zap dependency with a lightweight
stdLoggerthat writes structured lines with JSON fields. Samecontracts.Loggerinterface — drop-in replacement. AddedNewLoggerWithWriter(w io.Writer)for testability.Test Suite (37 tests, all passing)
sdk/contracts/filter_test.goIsCommandAllowed— nil filter, empty list, present, absent, multiple (5)sdk/contracts/mock_test.goMockMIDIClient— configured funcs, zero defaults, call counters (3)sdk/midi/options_setup_test.goapplyDefaultOptions— defaults, overrides, LogLevelIsSet (8)internal/logger/logger_wrapper_test.goAlso added
sdk/contracts/mock.go— aMockMIDIClientfor library consumers to use in their own tests.Files Changed