diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb8717b..20d083a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5.3.0 with: - go-version: 'stable' + go-version: '1.25.0' - name: Build run: ./tools/build.sh @@ -29,7 +29,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5.3.0 with: - go-version: 'stable' + go-version: '1.25.0' - name: Test run: go test -race ./... @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5.3.0 with: - go-version: 'stable' + go-version: '1.25.0' - name: golangci-lint uses: golangci/golangci-lint-action@v7.0.0 with: @@ -52,4 +52,21 @@ jobs: verify: false # This linter works based on the file modification when running under github actions because it's # not given full access to the git history and therefore gets the wrong date for all files. - args: --disable goheader \ No newline at end of file + args: --disable goheader + + fieldalignment: + name: fieldalignment + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5.3.0 + with: + go-version: '1.25.0' + + - name: install fieldalignment + run: go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest + + - name: check for diff + run: $GITHUB_WORKSPACE/tools/fieldalignment-lint.sh diff --git a/.golangci.yaml b/.golangci.yaml index f26e9e9..ebf9ca4 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -7,6 +7,7 @@ linters: disable: - cyclop - durationcheck + - embeddedstructfieldcheck # we use [fieldalignment] instead - errname - exhaustruct - forbidigo diff --git a/cmd/subcommands/acci-ping/gui.go b/cmd/subcommands/acci-ping/gui.go index 34f5295..aed679b 100644 --- a/cmd/subcommands/acci-ping/gui.go +++ b/cmd/subcommands/acci-ping/gui.go @@ -13,8 +13,8 @@ import ( type GUI struct { listeningChars map[rune]terminal.ConditionalListener - fallbacks []terminal.Listener GUIState *gui.GUIState + fallbacks []terminal.Listener } func newGUIState() *GUI { diff --git a/cmd/tab_completion/tab_completion.go b/cmd/tab_completion/tab_completion.go index 36d425c..79c86b4 100644 --- a/cmd/tab_completion/tab_completion.go +++ b/cmd/tab_completion/tab_completion.go @@ -29,8 +29,8 @@ const debug = false const AutoCompleteString = "b2827fc8fc8c8267cb15f5a925de7e4712aa04ef2fbd43458326b595d66a36d9" // sha256 of `autoCompleteString` type Command struct { - Cmd string Fs *tabflags.FlagSet + Cmd string } // Run will for a given command line input, write to stdout the autocomplete suggestion for the input [base] diff --git a/cmd/tab_completion/tabflags/tabflags.go b/cmd/tab_completion/tabflags/tabflags.go index db7acf7..ccceeed 100644 --- a/cmd/tab_completion/tabflags/tabflags.go +++ b/cmd/tab_completion/tabflags/tabflags.go @@ -16,27 +16,26 @@ import ( ) type AutoComplete struct { + FileExt string Choices []string WantsFile bool - FileExt string } type Flag struct { - // Name is the actual name as it appears on the CLI without the dash. - Name string // AutoComplete specifies if the flag has any auto complete options. AutoComplete *AutoComplete + // Name is the actual name as it appears on the CLI without the dash. + Name string } // FlagSet is an extension of [flag.FlagSet] that enables auto completion providers for a command. type FlagSet struct { *flag.FlagSet - flags []Flag - nameToAc map[string]*AutoComplete - - wantsFile bool + nameToAc map[string]*AutoComplete fileExt string + flags []Flag + wantsFile bool } // NewAutoCompleteFlagSet wraps a [flag.FlagSet] with autocomplete configuration, if [wantsFile] is set then diff --git a/graph/data/data.go b/graph/data/data.go index a2d5e54..4f4b05b 100644 --- a/graph/data/data.go +++ b/graph/data/data.go @@ -20,13 +20,13 @@ import ( ) type Data struct { - URL string Header *Header Network *Network + Runs *Runs + URL string InsertOrder []DataIndexes Blocks []*Block TotalCount int64 - Runs *Runs PingsMeta version } diff --git a/graph/data/data_test.go b/graph/data/data_test.go index 9e43bf7..2cbfe91 100644 --- a/graph/data/data_test.go +++ b/graph/data/data_test.go @@ -206,16 +206,15 @@ type BlockTest struct { } type DataTestCase struct { - Values []ping.PingResults - BlockSize int + ExpectedRuns data.Runs + BlockTest *BlockTest ExpectedGraphSpan data.TimeSpan + ExpectedSummary string + Values []ping.PingResults ExpectedGraphStats data.Stats + BlockSize int ExpectedPacketLoss float64 ExpectedTotalCount int - ExpectedRuns data.Runs - BlockTest *BlockTest - - ExpectedSummary string } // A fixed time stamp to make all testing relative too diff --git a/graph/data/serialisation_test.go b/graph/data/serialisation_test.go index 51cabe3..e98cbf5 100644 --- a/graph/data/serialisation_test.go +++ b/graph/data/serialisation_test.go @@ -170,9 +170,9 @@ func testCompacter(t th.T, start, empty data.Compact) { } type FileTest struct { + tz *time.Location FileName string ExpectedSummary string - tz *time.Location } func (ft FileTest) Run(t *testing.T) { diff --git a/graph/draw_window.go b/graph/draw_window.go index 3e96d49..3bdc1e9 100644 --- a/graph/draw_window.go +++ b/graph/draw_window.go @@ -62,12 +62,11 @@ const ( ) type label struct { + symbol string + text string coords - - symbol string - text string - leftJustify bool colour colour + leftJustify bool } type colour int diff --git a/graph/drawing.go b/graph/drawing.go index 2b82d79..97f4484 100644 --- a/graph/drawing.go +++ b/graph/drawing.go @@ -163,10 +163,10 @@ func translate(p ping.PingDataPoint, x *XAxisSpanInfo, y drawingYAxis, s termina } type gradientState struct { + lastGoodSpan *XAxisSpanInfo lastGoodIndex int64 lastGoodTerminalWidth int lastGoodTerminalHeight int - lastGoodSpan *XAxisSpanInfo } func (g gradientState) set(i int64, x, y int, s *XAxisSpanInfo) gradientState { diff --git a/graph/graph.go b/graph/graph.go index cb7cbe4..8fc3600 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -25,26 +25,18 @@ import ( ) type Graph struct { - Term *terminal.Terminal - - ui gui.GUI - - debugStrict bool - - sinkAlive bool - dataChannel <-chan ping.PingResults - - initial ping.PingsPerMinute - - data *graphdata.GraphData - - frameMutex *sync.Mutex - lastFrame frame - - drawingBuffer *draw.Buffer - + ui gui.GUI + Term *terminal.Terminal + dataChannel <-chan ping.PingResults + data *graphdata.GraphData + frameMutex *sync.Mutex + drawingBuffer *draw.Buffer presentation *controlState controlChannel <-chan Control + lastFrame frame + initial ping.PingsPerMinute + debugStrict bool + sinkAlive bool } // Control is the signal type which changes the presentation of the graph. @@ -54,8 +46,8 @@ type Control struct { } type Change[T any] struct { - DidChange bool Value T + DidChange bool } // Presentation is the dynamic part of the graph, these only change the presentation of the graph when drawn @@ -69,23 +61,22 @@ type Presentation struct { } type GraphConfiguration struct { + // Gui is the external GUI interface which the terminal will call [gui.Draw] on each frame if the gui + // component requires it. + Gui gui.GUI // Input will be owned by the graph and represents the source of data for the graph to plot. Input <-chan ping.PingResults // Terminal is the underlying terminal the graph will draw to and perform all external I/O too. The graph // takes ownership of the terminal. - Terminal *terminal.Terminal - // Gui is the external GUI interface which the terminal will call [gui.Draw] on each frame if the gui - // component requires it. - Gui gui.GUI - PingsPerMinute ping.PingsPerMinute + Terminal *terminal.Terminal + DrawingBuffer *draw.Buffer + ControlPlane <-chan Control + // Optional (can be nil) + Data *data.Data URL string - DrawingBuffer *draw.Buffer + PingsPerMinute ping.PingsPerMinute Presentation Presentation - ControlPlane <-chan Control DebugStrict bool - - // Optionals: - Data *data.Data } func StartUp() { @@ -288,13 +279,13 @@ func (g *Graph) checkf(shouldBeTrue bool, format string, a ...any) { } type frame struct { - PacketCount int64 - yAxis drawingYAxis xAxis drawingXAxis + spinnerData spinner framePainter func(io.Writer) error framePainterNoGui func(io.Writer) error - spinnerData spinner + yAxis drawingYAxis cfg computeFrameConfig + PacketCount int64 } func (f frame) Match(s terminal.Size, cfg computeFrameConfig) bool { @@ -307,7 +298,6 @@ func (f frame) Size() terminal.Size { } type controlState struct { - Presentation - m *sync.Mutex + Presentation } diff --git a/graph/graph_file_test.go b/graph/graph_file_test.go index d54df15..ddd4abb 100644 --- a/graph/graph_file_test.go +++ b/graph/graph_file_test.go @@ -39,11 +39,11 @@ const ( ) type FileTest struct { + TimeZoneOfFile *time.Location FileName string Sizes []terminal.Size - OnlyDoLinear bool - TimeZoneOfFile *time.Location TerminalWrapping th.TerminalWrapping + OnlyDoLinear bool } var StandardTestSizes = []terminal.Size{ diff --git a/graph/graph_test.go b/graph/graph_test.go index 4c08ba8..bb7df70 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -189,9 +189,9 @@ func TestThousandsDrawing(t *testing.T) { } type DrawingTest struct { - Size terminal.Size - Values []ping.PingDataPoint ExpectedFile string + Values []ping.PingDataPoint + Size terminal.Size } //nolint:unused diff --git a/graph/graphdata/graphdata.go b/graph/graphdata/graphdata.go index 3117905..7e0b5e9 100644 --- a/graph/graphdata/graphdata.go +++ b/graph/graphdata/graphdata.go @@ -25,9 +25,9 @@ import ( type GraphData struct { data *data.Data + m *sync.Mutex spans []*SpanInfo spanIndex int - m *sync.Mutex } func NewGraphData(d *data.Data) *GraphData { @@ -106,9 +106,9 @@ func (gd *GraphData) LockFreeIter(followLatestSpan bool) *Iter { } type Iter struct { - Total int64 d *data.Data spans Spans + Total int64 offset int64 } diff --git a/graph/graphdata/graphdata_test.go b/graph/graphdata/graphdata_test.go index 7ed4aef..d504eba 100644 --- a/graph/graphdata/graphdata_test.go +++ b/graph/graphdata/graphdata_test.go @@ -221,8 +221,8 @@ func (test TimeSpanTest) Run(t *testing.T) { type TimeSpanFileTest struct { File string - ExpectedSpanCount int ExpectedSpans []*data.TimeSpan + ExpectedSpanCount int } func (test TimeSpanFileTest) Run(t *testing.T) { diff --git a/graph/spinner.go b/graph/spinner.go index 3240d9d..a282e3d 100644 --- a/graph/spinner.go +++ b/graph/spinner.go @@ -23,8 +23,8 @@ var spinnerArray = [...]string{ } type spinner struct { - spinnerIndex int timestampLastDrawn time.Time + spinnerIndex int } func (s *spinner) spinner(size terminal.Size) string { diff --git a/graph/xAxis.go b/graph/xAxis.go index 9242ede..a4380f6 100644 --- a/graph/xAxis.go +++ b/graph/xAxis.go @@ -23,19 +23,19 @@ import ( ) type XAxisSpanInfo struct { - spans []*graphdata.SpanInfo spanStats *data.Stats pingStats *data.Stats timeSpan *data.TimeSpan + spans []*graphdata.SpanInfo startX int endX int width int } type drawingXAxis struct { - size int - spans []*XAxisSpanInfo overallSpan *data.TimeSpan + spans []*XAxisSpanInfo + size int } type xAxisIter struct { diff --git a/graph/yAxis.go b/graph/yAxis.go index 1742dfd..71f972c 100644 --- a/graph/yAxis.go +++ b/graph/yAxis.go @@ -22,8 +22,8 @@ import ( ) type drawingYAxis struct { - size int stats *data.Stats + size int labelSize int scale YAxisScale } diff --git a/gui/box_test.go b/gui/box_test.go index a5fccab..cc49176 100644 --- a/gui/box_test.go +++ b/gui/box_test.go @@ -214,11 +214,11 @@ func (tc *testCase) getOutputFileName() string { } type testCase struct { + text []string + position gui.Position size terminal.Size style gui.Style - position gui.Position align gui.HorizontalAlignment - text []string result hash } diff --git a/gui/notifications.go b/gui/notifications.go index 61f667b..5596f6b 100644 --- a/gui/notifications.go +++ b/gui/notifications.go @@ -25,9 +25,9 @@ import ( type Notification[T any] struct { m *sync.Mutex storage map[int]stored[T] - key int drawFunc func(terminal.Size, []T) Draw lastSize terminal.Size + key int } // NewNotification builds a new thread safe collection of [T]. The function passed will be called with a diff --git a/gui/themes/colours_test.go b/gui/themes/colours_test.go index 43e819f..be7e0aa 100644 --- a/gui/themes/colours_test.go +++ b/gui/themes/colours_test.go @@ -48,9 +48,9 @@ green component out of range -3, should be within 0 and 255`)}, } type CSSTest struct { + Err error input string R, G, B uint8 - Err error } func (tc CSSTest) Run(t *testing.T) { diff --git a/gui/themes/themes_test.go b/gui/themes/themes_test.go index 8f2fa00..6fda38f 100644 --- a/gui/themes/themes_test.go +++ b/gui/themes/themes_test.go @@ -86,10 +86,10 @@ func CurryTrueColour(r, g, b uint8) func(s string) string { } type AnsiThemeTest struct { - Input string - Output func(s string) string UnmarshalErr error ImplErr error + Output func(s string) string + Input string } const testString = "abcdefghijklmnopqrstuvwxyz" diff --git a/ping/addr.go b/ping/addr.go index 02b18ae..86095f5 100644 --- a/ping/addr.go +++ b/ping/addr.go @@ -9,10 +9,9 @@ package ping import "net" type addr struct { - ip net.IP - asUDP *net.UDPAddr asIP *net.IPAddr + ip net.IP } func New(addrType addressType, ip net.IP) *addr { diff --git a/ping/api.go b/ping/api.go index c94c949..c9548ea 100644 --- a/ping/api.go +++ b/ping/api.go @@ -22,16 +22,15 @@ import ( ) type Ping struct { + echoType icmp.Type + echoReply icmp.Type connect *icmp.PacketConn - addrType addressType - id uint16 + addresses *queryCache currentURL string + addrType addressType timeout time.Duration ratelimitTime time.Duration - - echoType, echoReply icmp.Type - - addresses *queryCache + id uint16 } // NewPing constructs a new Ping client which can perform accurate ping measurements. Either with @@ -183,20 +182,20 @@ func (p *Ping) CreateFlexibleChannel( } type PingResults struct { + // InternalErr represents some problem with [ping] package internal state which didn't gracefully handle + // some network problem. Other network problems which are expected and represent dropped packets **should + // be** handled gracefully and will be reported in the [PingDataPoint] field in the [Dropped]. + InternalErr error // Data is the data about this ping, containing the time taken for round trip or details if the packet was // dropped. Data PingDataPoint // IP is the address which this ping result was achieved from. IP net.IP - // InternalErr represents some problem with [ping] package internal state which didn't gracefully handle - // some network problem. Other network problems which are expected and represent dropped packets **should - // be** handled gracefully and will be reported in the [PingDataPoint] field in the [Dropped]. - InternalErr error } type PingDataPoint struct { - Duration time.Duration Timestamp time.Time + Duration time.Duration DropReason Dropped } diff --git a/ping/api_implementation.go b/ping/api_implementation.go index 347099a..e8280cd 100644 --- a/ping/api_implementation.go +++ b/ping/api_implementation.go @@ -198,8 +198,8 @@ func (pt pingTimeout) Error() string { return "PingTimeout {" + pt.String() + "} func (p *Ping) pingRead(ctx context.Context, buffer []byte) (int, error) { type read struct { - n int err error + n int } c := make(chan read) go func() { @@ -301,9 +301,8 @@ var listenList = []listenerConfig{ } type listenerConfig struct { - addressType - network, address string + addressType } type addressType int diff --git a/terminal/parser.go b/terminal/parser.go index 317e1e6..c8aff5f 100644 --- a/terminal/parser.go +++ b/terminal/parser.go @@ -142,10 +142,9 @@ type parser struct { Ctx context.Context ToStreamFrom io.Reader Buffer []byte - - parserHead int - pointer int - bufferSlice []byte + bufferSlice []byte + parserHead int + pointer int } // Consume the exact bytes passed, errors if the stream produced different bytes. diff --git a/terminal/terminal.go b/terminal/terminal.go index d7c5174..17dd847 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -14,7 +14,6 @@ import ( "slices" "strconv" "strings" - "sync" "github.com/Lexer747/acci-ping/gui/themes" "github.com/Lexer747/acci-ping/terminal/ansi" @@ -57,27 +56,20 @@ func ParseSize(s string) (Size, bool) { // - [NewParsedFixedSizeTerminal] // - [NewTestTerminal] type Terminal struct { - size atomic.Of[Size] - listeners []ConditionalListener - fallbacks []Listener - - stdin io.ReadWriter - stdout io.ReadWriter - - stdinFd int + stdin io.ReadWriter + stdout io.ReadWriter terminalSizeCallBack func() (Size, error) - - isTestTerminal bool - isDynamicSize bool - neverRaw bool - + // should be called if a panic occurs otherwise stacktraces are unreadable + cleanup func() + fallbacks []Listener + listeners []ConditionalListener + size atomic.Of[Size] + stdinFd int backgroundColour themes.Luminance backgroundDiscovered backgroundDiscovered - - // should be called if a panic occurs otherwise stacktraces are unreadable - cleanup func() - - listenMutex *sync.Mutex + isTestTerminal bool + isDynamicSize bool + neverRaw bool } // NewTerminal creates a new terminal which immediately tries to verify that it can operate for @@ -99,7 +91,6 @@ func NewTerminal() (*Terminal, error) { fallbacks: []Listener{}, stdin: os.Stdin, stdout: os.Stdout, - listenMutex: &sync.Mutex{}, isDynamicSize: true, stdinFd: int(os.Stdin.Fd()), terminalSizeCallBack: func() (Size, error) { @@ -126,7 +117,6 @@ func NewFixedSizeTerminal(s Size) (*Terminal, error) { fallbacks: []Listener{}, stdin: os.Stdin, stdout: os.Stdout, - listenMutex: &sync.Mutex{}, isDynamicSize: false, } return t, nil @@ -139,7 +129,6 @@ func NewDebuggingTerminal(s Size) (*Terminal, error) { fallbacks: []Listener{}, stdin: os.Stdin, stdout: os.Stdout, - listenMutex: &sync.Mutex{}, isDynamicSize: false, neverRaw: true, } @@ -183,7 +172,6 @@ func NewTestTerminal(stdinReader, stdoutWriter io.ReadWriter, terminalSizeCallBa terminalSizeCallBack: func() (Size, error) { return terminalSizeCallBack(), nil }, isTestTerminal: true, isDynamicSize: true, - listenMutex: &sync.Mutex{}, }, nil } @@ -204,21 +192,20 @@ func (t *Terminal) UpdateSize() error { } type Listener struct { - // Name is used for if a listener errors for easier identification, it may be omitted. - Name string // Action the callback which will be invoked when a user inputs the applicable rune, the rune passed is // the same rune passed to applicable. Note the terminal size will have been updated before this called, // but this is actually racey if the user is typing while changing size. If an error occurs in this action // the terminal will panic and exit. Action func(rune) error + // Name is used for if a listener errors for easier identification, it may be omitted. + Name string } type ConditionalListener struct { - Listener - // Applicable is the applicability of this listen, i.e. for which input runes do you want this action to // be fired Applicable func(rune) bool + Listener } type userControlCErr struct{} @@ -358,8 +345,8 @@ const ( ) type listenResult struct { - n int err error + n int } func (t *Terminal) beingListening(ctx context.Context) { diff --git a/tools/fieldalignment-lint.sh b/tools/fieldalignment-lint.sh new file mode 100755 index 0000000..298aca0 --- /dev/null +++ b/tools/fieldalignment-lint.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +ROOT=$(git rev-parse --show-toplevel) +pushd "$ROOT" &> /dev/null || exit + + +fieldalignment ./... &> /dev/null +exitCode=$? +if [[ $exitCode != 0 ]]; then + fieldalignment -fix -diff ./... +else + echo "fieldalignment good :)" +fi + +popd &> /dev/null || exit +exit $exitCode diff --git a/tools/verify.sh b/tools/verify.sh index 1d3f20f..ff02728 100755 --- a/tools/verify.sh +++ b/tools/verify.sh @@ -32,4 +32,5 @@ if [ $testsExitCode -eq 0 ] || [[ "$1" == "update" ]]; then find . -type d -empty -delete fi -"$ROOT"/tools/sub-command-test.sh \ No newline at end of file +"$ROOT"/tools/sub-command-test.sh +"$ROOT"/tools/fieldalignment-lint.sh \ No newline at end of file diff --git a/utils/numeric/numeric_test.go b/utils/numeric/numeric_test.go index 5073ddc..755ab60 100644 --- a/utils/numeric/numeric_test.go +++ b/utils/numeric/numeric_test.go @@ -21,10 +21,12 @@ import ( func TestNormalize(t *testing.T) { t.Parallel() type Case struct { - Min, Max float64 - NewMin, NewMax float64 - Inputs []float64 - Expected []float64 + Inputs []float64 + Expected []float64 + Min float64 + Max float64 + NewMin float64 + NewMax float64 } cases := []Case{ { diff --git a/utils/sliceutils/sliceutils_test.go b/utils/sliceutils/sliceutils_test.go index 08d6abc..5c8b411 100644 --- a/utils/sliceutils/sliceutils_test.go +++ b/utils/sliceutils/sliceutils_test.go @@ -67,8 +67,8 @@ func TestSplitN(t *testing.T) { type testCase[T comparable] struct { Input []T - SplitN int Output [][]T + SplitN int } func (tc testCase[T]) Run(t *testing.T) { diff --git a/utils/th/helper_debug.go b/utils/th/helper_debug.go index f469ad1..6c6c723 100644 --- a/utils/th/helper_debug.go +++ b/utils/th/helper_debug.go @@ -17,8 +17,8 @@ type debug struct { } type debugItem struct { - sequence []rune command string + sequence []rune startIndex int endIndex int } diff --git a/utils/th/test_helper.go b/utils/th/test_helper.go index abae937..50bc782 100644 --- a/utils/th/test_helper.go +++ b/utils/th/test_helper.go @@ -127,20 +127,19 @@ func EmulateTerminal(ansiText string, buffer []string, size terminal.Size, termi } type ansiState struct { - cursorRow, cursorColumn int + ansiText string // buffer is the in memory representation of the terminal, each entry of the slice is a row. - buffer []string - size terminal.Size - ansiText string - asRunes []rune - head int - outOfBounds bool - - terminalWrapping TerminalWrapping - + buffer []string + asRunes []rune // for errors in tests we accumulate this debug object which will store the runes processed to create the // current terminal. - debug debug + debug debug + size terminal.Size + cursorRow int + cursorColumn int + head int + terminalWrapping TerminalWrapping + outOfBounds bool } func (a *ansiState) peekN(n int) rune { return a.asRunes[a.head+n] }