pax_global_header00006660000000000000000000000064136544265140014524gustar00rootroot0000000000000052 comment=32215110dd91db8f50c1a22171f01c0f7a5d7b1c combinator-0.3.0/000077500000000000000000000000001365442651400136615ustar00rootroot00000000000000combinator-0.3.0/.github/000077500000000000000000000000001365442651400152215ustar00rootroot00000000000000combinator-0.3.0/.github/FUNDING.yml000066400000000000000000000000171365442651400170340ustar00rootroot00000000000000github: muesli combinator-0.3.0/.github/workflows/000077500000000000000000000000001365442651400172565ustar00rootroot00000000000000combinator-0.3.0/.github/workflows/go.yml000066400000000000000000000020021365442651400204000ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: test: strategy: matrix: go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} env: GO111MODULE: "on" steps: - name: Install Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v1 - name: Download Go modules run: go mod download - name: Build run: go build -v ./... - name: Test run: go test ./... - name: Coverage env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | go test -race -covermode atomic -coverprofile=profile.cov ./... GO111MODULE=off go get github.com/mattn/goveralls $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github if: matrix.go-version == '1.14.x' && matrix.platform == 'ubuntu-latest' combinator-0.3.0/.gitignore000066400000000000000000000004511365442651400156510ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Binaries example/example combinator-0.3.0/LICENSE000066400000000000000000000020671365442651400146730ustar00rootroot00000000000000MIT License Copyright (c) 2019 Christian Muehlhaeuser Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. combinator-0.3.0/README.md000066400000000000000000000043461365442651400151470ustar00rootroot00000000000000# combinator [![Latest Release](https://img.shields.io/github/release/muesli/combinator.svg)](https://github.com/muesli/combinator/releases) [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/muesli/combinator) [![Build Status](https://github.com/muesli/combinator/workflows/build/badge.svg)](https://github.com/muesli/combinator/actions) [![Coverage Status](https://coveralls.io/repos/github/muesli/combinator/badge.svg?branch=master)](https://coveralls.io/github/muesli/combinator?branch=master) [![Go ReportCard](http://goreportcard.com/badge/muesli/combinator)](http://goreportcard.com/report/muesli/combinator) `combinator` generates a slice of all possible value combinations for any given struct and a set of its potential member values. This can be used to generate extensive test matrixes among other things. ## Installation ```bash go get github.com/muesli/combinator ``` ## Example ```go type User struct { Name string Age uint Admin bool } /* Define potential test values. Make sure the struct's fields share the name and type of the structs you want to generate. */ testData := struct { Name []string Age []uint Admin []bool }{ Name: []string{"Alice", "Bob"}, Age: []uint{23, 42, 99}, Admin: []bool{false, true}, } // Generate all possible combinations var users []User combinator.Generate(&users, testData) for i, u := range users { fmt.Printf("Combination %2d | Name: %-5s | Age: %d | Admin: %v\n", i, u.Name, u.Age, u.Admin) } ``` ``` Combination 0 | Name: Alice | Age: 23 | Admin: false Combination 1 | Name: Bob | Age: 23 | Admin: false Combination 2 | Name: Alice | Age: 42 | Admin: false Combination 3 | Name: Bob | Age: 42 | Admin: false Combination 4 | Name: Alice | Age: 99 | Admin: false Combination 5 | Name: Bob | Age: 99 | Admin: false Combination 6 | Name: Alice | Age: 23 | Admin: true Combination 7 | Name: Bob | Age: 23 | Admin: true Combination 8 | Name: Alice | Age: 42 | Admin: true Combination 9 | Name: Bob | Age: 42 | Admin: true Combination 10 | Name: Alice | Age: 99 | Admin: true Combination 11 | Name: Bob | Age: 99 | Admin: true ``` ## License [MIT](https://github.com/muesli/combinator/raw/master/LICENSE) combinator-0.3.0/combinator.go000066400000000000000000000034511365442651400163500ustar00rootroot00000000000000package combinator import ( "fmt" "reflect" ) // Generate returns a slice of all possible value combinations for any given // struct and a set of its potential member values. func Generate(v interface{}, ov interface{}) error { // verify supplied types vPtr := reflect.ValueOf(v) if vPtr.Kind() != reflect.Ptr { return fmt.Errorf("non-pointer type supplied") } value := vPtr.Elem() if value.Kind() != reflect.Slice { return fmt.Errorf("pointer to non-slice type supplied") } vType := reflect.TypeOf(v).Elem().Elem() ovType := reflect.TypeOf(ov) ovValue := reflect.ValueOf(ov) if ovValue.Kind() != reflect.Struct { return fmt.Errorf("non-slice type supplied") } // calculate combinations combinations := 1 members := ovType.NumField() for i := 0; i < members; i++ { if ovValue.Field(i).Kind() != reflect.Slice { continue } if ovValue.Field(i).Len() == 0 { // ignore empty option values continue } fname := ovType.Field(i).Name if _, ok := vType.FieldByName(fname); !ok { return fmt.Errorf("can't access struct field %s", fname) } combinations *= ovValue.Field(i).Len() } // fill struct with all combinations for i := 0; i < combinations; i++ { vi := reflect.Indirect(reflect.New(vType)) offset := 1 for j := 0; j < members; j++ { ovf := ovValue.Field(j) var fvalue reflect.Value if ovf.Kind() == reflect.Slice { if ovf.Len() == 0 { // ignore empty option values continue } fvalue = ovf.Index((i / offset) % ovf.Len()) offset *= ovf.Len() } else { fvalue = ovf } fname := ovType.Field(j).Name if vi.FieldByName(fname).CanSet() { vi.FieldByName(fname).Set(fvalue) } // fmt.Println(fname, fvalue, offset) } // append item to original slice value.Set(reflect.Append(value, vi)) } return nil } combinator-0.3.0/combinator_test.go000066400000000000000000000060671365442651400174150ustar00rootroot00000000000000package combinator import ( "testing" ) func TestCombinator(t *testing.T) { type Data struct { Color string Number int Enabled bool // DataTests ignore this field Untouched bool } type DataTests struct { Color []string Number []int Enabled []bool // DataTests ignore this field Untouched []bool } td := DataTests{ Color: []string{"red", "green", "blue"}, Number: []int{0, 1}, Enabled: []bool{false, true}, // DataTests ignore this field Untouched: []bool{}, } tdl := len(td.Color) * len(td.Number) * len(td.Enabled) // initialize check matrix matrix := map[string]map[int]map[bool]bool{} for ci := 0; ci < len(td.Color); ci++ { matrix[td.Color[ci]] = map[int]map[bool]bool{} for ni := 0; ni < len(td.Number); ni++ { matrix[td.Color[ci]][td.Number[ni]] = map[bool]bool{} for bi := 0; bi < len(td.Enabled); bi++ { matrix[td.Color[ci]][td.Number[ni]][td.Enabled[bi]] = false } } } var data []Data err := Generate(&data, td) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(data) != tdl { t.Errorf("expected %d permutations, got %d", tdl, len(data)) } for _, v := range data { // fmt.Println(i, v) cmatrix, ok := matrix[v.Color] if !ok { t.Errorf("unexpected value %s", v.Color) continue } nmatrix, ok := cmatrix[v.Number] if !ok { t.Errorf("unexpected value %s %d", v.Color, v.Number) continue } _, ok = nmatrix[v.Enabled] if !ok { t.Errorf("unexpected value %s %d %v", v.Color, v.Number, v.Enabled) continue } // flag combination as found matrix[v.Color][v.Number][v.Enabled] = true } // check all combinations have been found for ck, cv := range matrix { for nk, nv := range cv { for bk, bv := range nv { if !bv { t.Errorf("combination not found: %s %d %v", ck, nk, bk) } } } } } func TestConstValue(t *testing.T) { type Data struct { Number int Enabled bool } type DataTests struct { Number []int Enabled bool } td := DataTests{ Number: []int{0, 1}, Enabled: true, } var data []Data err := Generate(&data, td) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, v := range data { if !v.Enabled { t.Fatalf("expected enabled to be set to true") } } } func TestUnmatchedField(t *testing.T) { type Data struct { Number int } type DataTests struct { Number []int // Data does not actually contain this field Unmatched []bool } td := DataTests{ Number: []int{0, 1}, // Data does not actually contain this field Unmatched: []bool{false}, } var data []Data err := Generate(&data, td) if err == nil { t.Errorf("expected error for unmatched fields in data, got nil") } } func TestInvalidType(t *testing.T) { td := struct { Number []int }{} var s string err := Generate(s, td) if err == nil { t.Errorf("expected error for non-pointer type, got nil") } err = Generate(&s, td) if err == nil { t.Errorf("expected error for invalid type, got nil") } err = Generate(&[]string{}, "") if err == nil { t.Errorf("expected error for invalid type, got nil") } } combinator-0.3.0/example/000077500000000000000000000000001365442651400153145ustar00rootroot00000000000000combinator-0.3.0/example/main.go000066400000000000000000000012421365442651400165660ustar00rootroot00000000000000package main import ( "fmt" "github.com/muesli/combinator" ) func main() { type User struct { Name string Age uint Admin bool } /* Define potential test values. Make sure the struct's fields share the name and type of the structs you want to generate. */ testData := struct { Name []string Age []uint Admin []bool }{ Name: []string{"Alice", "Bob"}, Age: []uint{23, 42, 99}, Admin: []bool{false, true}, } // Generate all possible combinations var users []User combinator.Generate(&users, testData) for i, u := range users { fmt.Printf("Combination %2d | Name: %-5s | Age: %d | Admin: %v\n", i, u.Name, u.Age, u.Admin) } } combinator-0.3.0/go.mod000066400000000000000000000000551365442651400147670ustar00rootroot00000000000000module github.com/muesli/combinator go 1.14