azuline / cmponly

Partial struct equality assertions for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

cmponly

Partial struct equality assertions in Go.

cmponly provides a cmponly.Fields function, which can be used as an Option in go-cmp's cmp.Diff. cmponly.Fields allows specifying a subset of struct fields to compare. cmponly.Fields is the inverse of cmpopts.IgnoreFields.

So for example:

import (
  "testing"
  "github.com/azuline/cmponly/cmponly"
  "github.com/google/go-cmp/cmp"
)

type Record struct {
  A string
  B string
}

func ExampleTest(t *testing.T) {
  want := Record{A: "aaaa"}
  got := functionUnderTest()

  if diff := cmp.Diff(want, got, cmponly.Fields(Record{}, "A")); diff != "" {
    t.Fatalf("(-want +got):\n%s", diff)
  }
}

However, this pattern comes with a footgun: if we refactor Record's fields to be named C and D instead, and forget to update cmponly.Fields's parameters, cmponly.Fields will not autorefactor or fail typechecking. cmp.Diff will compare 0 fields and vacuously pass.

The cmponlylint linter solves this footgun. This linter checks that all specified fields are valid fields on the struct being compared. So in the above example, the linter would fail with:

example.go:9:32 specified fields do not exist on struct: A

Motivation & Tradeoffs

brandur's PartialEqual describes the motivation well, and is another solution to the same problem of partial struct equality assertions.

However, after playing around with brandur's PartialEqual, I thought the zero-values footgun too dangerous. cmponly is an alternative with different tradeoffs.

I find the tradeoffs in cmponly to be more palatable, as cmponly:

  1. Supports comparing zero-values.
  2. Is refactoring semi-friendly, as the included linter catches invalid fields.

However, this package has a few cons:

  • Fields must be specified twice, once in the want struct, and again in the cmponly.Fields function call.
  • It is refactoring semi-unfriendly, as LSP does not recognize stringified field names.
  • It requires an additional linter.

Usage

TODO: standalone

TODO: golangci-lint

Development & Builds

A Nix dev shell is provided to configure the development environment.

Tests can be ran with make test. Note that since analysistest does not support Go modules, we have instead implemented a hacky test harness (./test-linter.sh) that checks the output of the self-contained linter command.

The golangci-lint plugin can be built with the Nix environment:

$ nix build .#packages.x86_64-linux.golangci-lint-plugin
$ ls result/lib
cmponlylint-plugin.so

License

Copyright 2023 blissful <blissful@sunsetglow.net>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

About

Partial struct equality assertions for Go

License:Apache License 2.0


Languages

Language:Go 70.8%Language:Nix 23.0%Language:Shell 3.8%Language:Makefile 2.5%