From 83cf0e78359e6b7865ae11b3722fcf27a1ce4d31 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 9 Apr 2025 01:31:28 +0800 Subject: [PATCH 1/3] new package: x/test --- go.mod | 2 +- test/_match_test.go | 24 ++ test/case.go | 156 ++++++++++++ test/logt/logt.go | 167 ++++++++++++ test/match.go | 604 ++++++++++++++++++++++++++++++++++++++++++++ test/panic.go | 31 +++ 6 files changed, 983 insertions(+), 1 deletion(-) create mode 100644 test/_match_test.go create mode 100644 test/case.go create mode 100644 test/logt/logt.go create mode 100644 test/match.go create mode 100644 test/panic.go diff --git a/go.mod b/go.mod index 740455b..b1a3831 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/qiniu/x -go 1.13 +go 1.18 retract ( v7.0.0+incompatible diff --git a/test/_match_test.go b/test/_match_test.go new file mode 100644 index 0000000..3e68813 --- /dev/null +++ b/test/_match_test.go @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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. + */ + +package test + +import ( + "testing" +) + +func TestBasic(t *testing.T) { +} diff --git a/test/case.go b/test/case.go new file mode 100644 index 0000000..3cf6d4f --- /dev/null +++ b/test/case.go @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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. + */ + +package test + +import ( + "testing" + "time" +) + +// ----------------------------------------------------------------------------- + +// CaseT is an interface that defines the methods for a test case. +type CaseT interface { + // Name returns the name of the running (sub-) test or benchmark. + // + // The name will include the name of the test along with the names of + // any nested sub-tests. If two sibling sub-tests have the same name, + // Name will append a suffix to guarantee the returned name is unique. + Name() string + + // Fail marks the function as having failed but continues execution. + Fail() + + // Failed reports whether the function has failed. + Failed() bool + + // FailNow marks the function as having failed and stops its execution + // by calling runtime.Goexit (which then runs all deferred calls in the + // current goroutine). + // Execution will continue at the next test or benchmark. + // FailNow must be called from the goroutine running the + // test or benchmark function, not from other goroutines + // created during the test. Calling FailNow does not stop + // those other goroutines. + FailNow() + + // Log formats its arguments using default formatting, analogous to Println, + // and records the text in the error log. For tests, the text will be printed only if + // the test fails or the -test.v flag is set. For benchmarks, the text is always + // printed to avoid having performance depend on the value of the -test.v flag. + Log(args ...any) + + // Logf formats its arguments according to the format, analogous to Printf, and + // records the text in the error log. A final newline is added if not provided. For + // tests, the text will be printed only if the test fails or the -test.v flag is + // set. For benchmarks, the text is always printed to avoid having performance + // depend on the value of the -test.v flag. + Logf(format string, args ...any) + + // Errorln is equivalent to Log followed by Fail. + Errorln(args ...any) + + // Errorf is equivalent to Logf followed by Fail. + Errorf(format string, args ...any) + + // Fatal is equivalent to Log followed by FailNow. + Fatal(args ...any) + + // Fatalf is equivalent to Logf followed by FailNow. + Fatalf(format string, args ...any) + + // Skip is equivalent to Log followed by SkipNow. + Skip(args ...any) + + // Skipf is equivalent to Logf followed by SkipNow. + Skipf(format string, args ...any) + + // SkipNow marks the test as having been skipped and stops its execution + // by calling runtime.Goexit. + // If a test fails (see Error, Errorf, Fail) and is then skipped, + // it is still considered to have failed. + // Execution will continue at the next test or benchmark. See also FailNow. + // SkipNow must be called from the goroutine running the test, not from + // other goroutines created during the test. Calling SkipNow does not stop + // those other goroutines. + SkipNow() + + // Skipped reports whether the test was skipped. + Skipped() bool + + // Helper marks the calling function as a test helper function. + // When printing file and line information, that function will be skipped. + // Helper may be called simultaneously from multiple goroutines. + Helper() + + // Cleanup registers a function to be called when the test (or subtest) and all its + // subtests complete. Cleanup functions will be called in last added, + // first called order. + Cleanup(f func()) + + // TempDir returns a temporary directory for the test to use. + // The directory is automatically removed by Cleanup when the test and + // all its subtests complete. + // Each subsequent call to t.TempDir returns a unique directory; + // if the directory creation fails, TempDir terminates the test by calling Fatal. + TempDir() string + + // Run runs f as a subtest of t called name. + // + // Run may be called simultaneously from multiple goroutines, but all such calls + // must return before the outer test function for t returns. + Run(name string, f func()) bool + + // Deadline reports the time at which the test binary will have + // exceeded the timeout specified by the -timeout flag. + // + // The ok result is false if the -timeout flag indicates “no timeout” (0). + Deadline() (deadline time.Time, ok bool) +} + +// ----------------------------------------------------------------------------- + +// TestingT is a wrapper for testing.T. +// It implements the CaseT interface and provides methods to manage test state +// and support formatted test logs. +type TestingT struct { + *testing.T +} + +// NewT creates a testing object. +func NewT(t *testing.T) TestingT { + return TestingT{t} +} + +// Errorln is equivalent to Log followed by Fail. +func (p TestingT) Errorln(args ...any) { + t := p.T + t.Helper() + t.Error(args...) +} + +// Run runs f as a subtest of t called name. +// +// Run may be called simultaneously from multiple goroutines, but all such calls +// must return before the outer test function for t returns. +func (p TestingT) Run(name string, doSth func()) bool { + return p.T.Run(name, func(t *testing.T) { + doSth() + }) +} + +// ----------------------------------------------------------------------------- diff --git a/test/logt/logt.go b/test/logt/logt.go new file mode 100644 index 0000000..d438239 --- /dev/null +++ b/test/logt/logt.go @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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. + */ + +package logt + +import ( + "log" + "time" +) + +type T struct { + name string + fail bool + skipped bool +} + +func New() *T { + return &T{} +} + +func (p *T) Name() string { + return p.name +} + +// Fail marks the function as having failed but continues execution. +func (p *T) Fail() { + p.fail = true +} + +// Failed reports whether the function has failed. +func (p *T) Failed() bool { + return p.fail +} + +// FailNow marks the function as having failed and stops its execution +// by calling runtime.Goexit (which then runs all deferred calls in the +// current goroutine). +// Execution will continue at the next test or benchmark. +// FailNow must be called from the goroutine running the +// test or benchmark function, not from other goroutines +// created during the test. Calling FailNow does not stop +// those other goroutines. +func (p *T) FailNow() { + p.fail = true + panic("todo") +} + +// Log formats its arguments using default formatting, analogous to Println, +// and records the text in the error log. For tests, the text will be printed only if +// the test fails or the -test.v flag is set. For benchmarks, the text is always +// printed to avoid having performance depend on the value of the -test.v flag. +func (p *T) Log(args ...any) { + log.Println(args...) +} + +// Logf formats its arguments according to the format, analogous to Printf, and +// records the text in the error log. A final newline is added if not provided. For +// tests, the text will be printed only if the test fails or the -test.v flag is +// set. For benchmarks, the text is always printed to avoid having performance +// depend on the value of the -test.v flag. +func (p *T) Logf(format string, args ...any) { + log.Printf(format, args...) +} + +// Errorln is equivalent to Log followed by Fail. +func (p *T) Errorln(args ...any) { + log.Println(args...) + p.Fail() +} + +// Errorf is equivalent to Logf followed by Fail. +func (p *T) Errorf(format string, args ...any) { + log.Printf(format, args...) + p.Fail() +} + +// Fatal is equivalent to Log followed by FailNow. +func (p *T) Fatal(args ...any) { + log.Panicln(args...) +} + +// Fatalf is equivalent to Logf followed by FailNow. +func (p *T) Fatalf(format string, args ...any) { + log.Panicf(format, args...) +} + +// Skip is equivalent to Log followed by SkipNow. +func (p *T) Skip(args ...any) { + log.Println(args...) + p.SkipNow() +} + +// Skipf is equivalent to Logf followed by SkipNow. +func (p *T) Skipf(format string, args ...any) { + log.Printf(format, args...) + p.SkipNow() +} + +// SkipNow marks the test as having been skipped and stops its execution +// by calling runtime.Goexit. +// If a test fails (see Error, Errorf, Fail) and is then skipped, +// it is still considered to have failed. +// Execution will continue at the next test or benchmark. See also FailNow. +// SkipNow must be called from the goroutine running the test, not from +// other goroutines created during the test. Calling SkipNow does not stop +// those other goroutines. +func (p *T) SkipNow() { + p.skipped = true +} + +// Skipped reports whether the test was skipped. +func (p *T) Skipped() bool { + return p.skipped +} + +// Helper marks the calling function as a test helper function. +// When printing file and line information, that function will be skipped. +// Helper may be called simultaneously from multiple goroutines. +func (p *T) Helper() { +} + +// Cleanup registers a function to be called when the test (or subtest) and all its +// subtests complete. Cleanup functions will be called in last added, +// first called order. +func (p *T) Cleanup(f func()) { + // TODO: +} + +// TempDir returns a temporary directory for the test to use. +// The directory is automatically removed by Cleanup when the test and +// all its subtests complete. +// Each subsequent call to t.TempDir returns a unique directory; +// if the directory creation fails, TempDir terminates the test by calling Fatal. +func (p *T) TempDir() string { + panic("todo") +} + +// Run runs f as a subtest of t called name. +// +// Run may be called simultaneously from multiple goroutines, but all such calls +// must return before the outer test function for t returns. +func (p *T) Run(name string, f func()) bool { + p.name = name + f() + return true +} + +// Deadline reports the time at which the test binary will have +// exceeded the timeout specified by the -timeout flag. +// +// The ok result is false if the -timeout flag indicates “no timeout” (0). +func (p *T) Deadline() (deadline time.Time, ok bool) { + panic("todo") +} diff --git a/test/match.go b/test/match.go new file mode 100644 index 0000000..9b577b7 --- /dev/null +++ b/test/match.go @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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. + */ + +package test + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" +) + +const ( + GopPackage = true +) + +type basetype interface { + string | int | bool | float64 +} + +func toMapAny[T basetype](val map[string]T) map[string]any { + ret := make(map[string]any, len(val)) + for k, v := range val { + ret[k] = v + } + return ret +} + +func tryToMapAny(val any) (ret map[string]any, ok bool) { + v := reflect.ValueOf(val) + return castMapAny(v) +} + +func castMapAny(v reflect.Value) (ret map[string]any, ok bool) { + if v.Kind() != reflect.Map || v.Type().Key() != tyString { + return + } + ret, ok = make(map[string]any, v.Len()), true + for it := v.MapRange(); it.Next(); { + key := it.Key().String() + ret[key] = it.Value().Interface() + } + return +} + +var ( + tyString = reflect.TypeOf("") +) + +// ----------------------------------------------------------------------------- + +type baseelem interface { + string +} + +type baseslice interface { + []string +} + +type TySet[T baseelem] []T +type TyAnySet []any + +func Set__0[T baseelem](vals ...T) TySet[T] { + return TySet[T](vals) +} + +func Set__1[T []string](v *Var__3[T]) TySet[string] { + return TySet[string](v.Val()) +} + +func Set__2(vals ...any) TyAnySet { + return TyAnySet(vals) +} + +// ----------------------------------------------------------------------------- + +// Case represents a test case. +type Case struct { + CaseT +} + +func nameCtx(name []string) string { + if name != nil { + return " (" + strings.Join(name, ".") + ")" + } + return "" +} + +const ( + Gopo_Gopt_Case_Match = "Gopt_Case_MatchTBase,Gopt_Case_MatchMap,Gopt_Case_MatchSlice,Gopt_Case_MatchBaseSlice,Gopt_Case_MatchSet,Gopt_Case_MatchAnySet,Gopt_Case_MatchAny" +) + +func Gopt_Case_MatchTBase[T basetype](t CaseT, expected, got T, name ...string) { + if expected != got { + t.Helper() + t.Fatalf("unmatched value%s - expected: %v, got: %v\n", nameCtx(name), expected, got) + } +} + +func Gopt_Case_MatchMap(t CaseT, expected, got map[string]any, name ...string) { + t.Helper() + idx := len(name) + name = append(name, "") + for key, ev := range expected { + name[idx] = key + Gopt_Case_MatchAny(t, ev, got[key], name...) + } +} + +func Gopt_Case_MatchSlice(t CaseT, expected, got []any, name ...string) { + t.Helper() + if len(expected) != len(got) { + t.Fatalf("unmatched slice%s length - expected: %d, got: %d\n", nameCtx(name), len(expected), len(got)) + } + idx := len(name) - 1 + if idx < 0 { + idx, name = 0, []string{"$"} + } + for i, ev := range expected { + name[idx] = "[" + strconv.Itoa(i) + "]" + Gopt_Case_MatchAny(t, ev, got[i], name...) + } +} + +func Gopt_Case_MatchBaseSlice[T baseelem](t CaseT, expected, got []T, name ...string) { + t.Helper() + if len(expected) != len(got) { + t.Fatalf("unmatched slice%s length - expected: %d, got: %d\n", nameCtx(name), len(expected), len(got)) + } + idx := len(name) - 1 + if idx < 0 { + idx, name = 0, []string{"$"} + } + for i, ev := range expected { + name[idx] = "[" + strconv.Itoa(i) + "]" + Gopt_Case_MatchTBase(t, ev, got[i], name...) + } +} + +func Gopt_Case_MatchSet[T baseelem](t CaseT, expected TySet[T], got []T, name ...string) { + if len(expected) != len(got) { + t.Fatalf("unmatched set%s length - expected: %d, got: %d\n", nameCtx(name), len(expected), len(got)) + } + for _, gv := range got { + if !hasElem(gv, expected) { + t.Fatalf("unmatched set%s: expected: %v, value %v doesn't exist in it\n", nameCtx(name), expected, gv) + } + } +} + +func Gopt_Case_MatchAnySet(t CaseT, expected TyAnySet, got any, name ...string) { + if gv, ok := got.([]any); ok { + matchAnySet(t, expected, gv) + return + } + vgot := reflect.ValueOf(got) + if vgot.Kind() != reflect.Slice { + t.Fatalf("unmatched set%s: expected: %v, got a non slice value: %v\n", nameCtx(name), expected, got) + } + for i, n := 0, vgot.Len(); i < n; i++ { + gv := vgot.Index(i).Interface() + if !hasAnyElem(gv, expected) { + t.Fatalf("unmatched set%s: expected: %v, value %v doesn't exist in it\n", nameCtx(name), expected, gv) + } + } +} + +func matchAnySet(t CaseT, expected TyAnySet, got []any, name ...string) { + if len(expected) != len(got) { + t.Fatalf("unmatched set%s length - expected: %d, got: %d\n", nameCtx(name), len(expected), len(got)) + } + for _, gv := range got { + if !hasAnyElem(gv, expected) { + t.Fatalf("unmatched set%s: expected: %v, value %v doesn't exist in it\n", nameCtx(name), expected, gv) + } + } +} + +func hasElem[T baseelem](v T, expected []T) bool { + for _, ev := range expected { + if reflect.DeepEqual(v, ev) { + return true + } + } + return false +} + +func hasAnyElem(v any, expected []any) bool { + for _, ev := range expected { + if v == ev { + return true + } + } + return false +} + +func Gopt_Case_MatchAny(t CaseT, expected, got any, name ...string) { + t.Helper() +retry: + switch ev := expected.(type) { + case string: + switch gv := got.(type) { + case string: + Gopt_Case_MatchTBase(t, ev, gv, name...) + return + case *Var__0[string]: + Gopt_Case_MatchTBase(t, ev, gv.Val(), name...) + return + } + case int: + switch gv := got.(type) { + case int: + Gopt_Case_MatchTBase(t, ev, gv, name...) + return + case *Var__0[int]: + Gopt_Case_MatchTBase(t, ev, gv.Val(), name...) + return + } + case bool: + switch gv := got.(type) { + case bool: + Gopt_Case_MatchTBase(t, ev, gv, name...) + return + case *Var__0[bool]: + Gopt_Case_MatchTBase(t, ev, gv.Val(), name...) + return + } + case float64: + switch gv := got.(type) { + case float64: + Gopt_Case_MatchTBase(t, ev, gv, name...) + return + case *Var__0[float64]: + Gopt_Case_MatchTBase(t, ev, gv.Val(), name...) + return + } + case map[string]any: + switch gv := got.(type) { + case map[string]any: + Gopt_Case_MatchMap(t, ev, gv, name...) + return + case *Var__1[map[string]any]: + Gopt_Case_MatchMap(t, ev, gv.Val(), name...) + return + default: + if gv, ok := tryToMapAny(got); ok { + Gopt_Case_MatchMap(t, ev, gv, name...) + return + } + } + case []any: + switch gv := got.(type) { + case []any: + Gopt_Case_MatchSlice(t, ev, gv, name...) + return + case *Var__2[[]any]: + Gopt_Case_MatchSlice(t, ev, gv.Val(), name...) + return + } + case []string: + switch gv := got.(type) { + case []string: + Gopt_Case_MatchBaseSlice(t, ev, gv, name...) + return + case *Var__3[[]string]: + Gopt_Case_MatchBaseSlice(t, ev, gv.Val(), name...) + return + } + case TySet[string]: + switch gv := got.(type) { + case []string: + Gopt_Case_MatchSet(t, ev, gv, name...) + return + case *Var__3[[]string]: + Gopt_Case_MatchSet(t, ev, gv.Val(), name...) + return + } + case TyAnySet: + switch gv := got.(type) { + case *Var__2[[]any]: + Gopt_Case_MatchAnySet(t, ev, gv.Val(), name...) + return + default: + Gopt_Case_MatchAnySet(t, ev, gv, name...) + return + } + case *Var__0[string]: + switch gv := got.(type) { + case string: + ev.Match(t, gv, name...) + return + case *Var__0[string]: + ev.Match(t, gv.Val(), name...) + return + case nil: + ev.MatchNil(t, name...) + return + } + case *Var__0[int]: + switch gv := got.(type) { + case int: + ev.Match(t, gv, name...) + return + case *Var__0[int]: + ev.Match(t, gv.Val(), name...) + return + case nil: + ev.MatchNil(t, name...) + return + } + case *Var__0[bool]: + switch gv := got.(type) { + case bool: + ev.Match(t, gv, name...) + return + case *Var__0[bool]: + ev.Match(t, gv.Val(), name...) + return + case nil: + ev.MatchNil(t, name...) + return + } + case *Var__0[float64]: + switch gv := got.(type) { + case float64: + ev.Match(t, gv, name...) + return + case *Var__0[float64]: + ev.Match(t, gv.Val(), name...) + return + case nil: + ev.MatchNil(t, name...) + return + } + case *Var__1[map[string]any]: + switch gv := got.(type) { + case map[string]any: + ev.Match(t, gv, name...) + return + case *Var__1[map[string]any]: + ev.Match(t, gv.Val(), name...) + return + } + case *Var__2[[]any]: + switch gv := got.(type) { + case []any: + ev.Match(t, gv, name...) + return + case *Var__2[[]any]: + ev.Match(t, gv.Val(), name...) + return + } + case *Var__3[[]string]: + switch gv := got.(type) { + case []string: + ev.Match(t, gv, name...) + return + case *Var__3[[]string]: + ev.Match(t, gv.Val(), name...) + return + } + + // fallback types: + case map[string]string: + expected = toMapAny(ev) + goto retry + case map[string]int: + expected = toMapAny(ev) + goto retry + case map[string]bool: + expected = toMapAny(ev) + goto retry + case map[string]float64: + expected = toMapAny(ev) + goto retry + + // other types: + default: + if v, ok := tryToMapAny(expected); ok { + expected = v + goto retry + } + if reflect.DeepEqual(expected, got) { + return + } + } + t.Fatalf( + "unmatched%s - expected: %v (%T), got: %v (%T)\n", + nameCtx(name), expected, expected, got, got, + ) +} + +// ----------------------------------------------------------------------------- + +type Var__0[T basetype] struct { + val T + valid bool +} + +func (p *Var__0[T]) check() { + if !p.valid { + Fatal("read variable value before initialization") + } +} + +func (p *Var__0[T]) Ok() bool { + return p.valid +} + +func (p *Var__0[T]) String() string { + p.check() + return fmt.Sprint(p.val) // TODO: optimization +} + +func (p *Var__0[T]) Val() T { + p.check() + return p.val +} + +func (p *Var__0[T]) MarshalJSON() ([]byte, error) { + p.check() + return json.Marshal(p.val) +} + +func (p *Var__0[T]) UnmarshalJSON(data []byte) error { + p.valid = true + return json.Unmarshal(data, &p.val) +} + +func (p *Var__0[T]) Equal(t CaseT, v T) bool { + p.check() + return p.val == v +} + +func (p *Var__0[T]) Match(t CaseT, v T, name ...string) { + if !p.valid { + p.val, p.valid = v, true + return + } + t.Helper() + Gopt_Case_MatchTBase(t, p.val, v, name...) +} + +func (p *Var__0[T]) MatchNil(t CaseT, name ...string) { + if p.valid { + t.Helper() + t.Fatalf("unmatched%s - expected: nil, got: %v\n", nameCtx(name), p.val) + } +} + +// ----------------------------------------------------------------------------- + +type Var__1[T map[string]any] struct { + val T +} + +func (p *Var__1[T]) check() { + if p.val == nil { + Fatal("read variable value before initialization") + } +} + +func (p *Var__1[T]) Ok() bool { + return p.val != nil +} + +func (p *Var__1[T]) Val() T { + p.check() + return p.val +} + +func (p *Var__1[T]) MarshalJSON() ([]byte, error) { + p.check() + return json.Marshal(p.val) +} + +func (p *Var__1[T]) UnmarshalJSON(data []byte) error { + return json.Unmarshal(data, &p.val) +} + +func (p *Var__1[T]) Match(t CaseT, v T, name ...string) { + if p.val == nil { + p.val = v + return + } + t.Helper() + Gopt_Case_MatchMap(t, p.val, v, name...) +} + +// ----------------------------------------------------------------------------- + +type Var__2[T []any] struct { + val T + valid bool +} + +func (p *Var__2[T]) check() { + if !p.valid { + Fatal("read variable value before initialization") + } +} + +func (p *Var__2[T]) Ok() bool { + return p.valid +} + +func (p *Var__2[T]) Val() T { + p.check() + return p.val +} + +func (p *Var__2[T]) MarshalJSON() ([]byte, error) { + p.check() + return json.Marshal(p.val) +} + +func (p *Var__2[T]) UnmarshalJSON(data []byte) error { + p.valid = true + return json.Unmarshal(data, &p.val) +} + +func (p *Var__2[T]) Match(t CaseT, v T, name ...string) { + if p.val == nil { + p.val, p.valid = v, true + return + } + t.Helper() + Gopt_Case_MatchSlice(t, p.val, v, name...) +} + +// ----------------------------------------------------------------------------- + +type Var__3[T baseslice] struct { + val T + valid bool +} + +func (p *Var__3[T]) check() { + if !p.valid { + Fatal("read variable value before initialization") + } +} + +func (p *Var__3[T]) Ok() bool { + return p.valid +} + +func (p *Var__3[T]) Val() T { + p.check() + return p.val +} + +func (p *Var__3[T]) MarshalJSON() ([]byte, error) { + p.check() + return json.Marshal(p.val) +} + +func (p *Var__3[T]) UnmarshalJSON(data []byte) error { + p.valid = true + return json.Unmarshal(data, &p.val) +} + +func (p *Var__3[T]) Match(t CaseT, v T, name ...string) { + if p.val == nil { + p.val, p.valid = v, true + return + } + t.Helper() + Gopt_Case_MatchBaseSlice(t, p.val, v, name...) +} + +// ----------------------------------------------------------------------------- + +func Gopx_Var_Cast__0[T basetype]() *Var__0[T] { + return new(Var__0[T]) +} + +func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] { + return new(Var__1[T]) +} + +func Gopx_Var_Cast__2[T []any]() *Var__2[T] { + return new(Var__2[T]) +} + +func Gopx_Var_Cast__3[T []string]() *Var__3[T] { + return new(Var__3[T]) +} + +// ----------------------------------------------------------------------------- diff --git a/test/panic.go b/test/panic.go new file mode 100644 index 0000000..5824d11 --- /dev/null +++ b/test/panic.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * 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. + */ + +package test + +import ( + "log" +) + +// Fatal is a wrapper for log.Panicln. +func Fatal(v ...any) { + log.Panicln(v...) +} + +// Fatalf is a wrapper for log.Panicf. +func Fatalf(format string, v ...any) { + log.Panicf(format, v...) +} From 039c7b0aecba110ad07c35c96b5fb3bf708e0c56 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 9 Apr 2025 01:36:46 +0800 Subject: [PATCH 2/3] codecov --- .github/codecov.yml | 4 ++++ .github/workflows/go.yml | 2 +- test/logt/logt.go | 2 +- test/match.go | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..006d00d --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,4 @@ +coverage: + ignore: + - "test" + - "ts" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6d455a9..3e5a7d6 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,7 +10,7 @@ jobs: Test: strategy: matrix: - go-version: [1.17.x, 1.18.x, 1.20.x, 1.21.x] + go-version: [1.18.x, 1.20.x, 1.21.x, 1.22.x] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: diff --git a/test/logt/logt.go b/test/logt/logt.go index d438239..b0d1d7d 100644 --- a/test/logt/logt.go +++ b/test/logt/logt.go @@ -136,7 +136,7 @@ func (p *T) Helper() { // subtests complete. Cleanup functions will be called in last added, // first called order. func (p *T) Cleanup(f func()) { - // TODO: + // TODO(xsw): } // TempDir returns a temporary directory for the test to use. diff --git a/test/match.go b/test/match.go index 9b577b7..b9e3c98 100644 --- a/test/match.go +++ b/test/match.go @@ -423,7 +423,7 @@ func (p *Var__0[T]) Ok() bool { func (p *Var__0[T]) String() string { p.check() - return fmt.Sprint(p.val) // TODO: optimization + return fmt.Sprint(p.val) // TODO(xsw): optimization } func (p *Var__0[T]) Val() T { From 3dfff9b7d111ff7a9c075408b7a6609ef1c3c633 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 9 Apr 2025 01:39:46 +0800 Subject: [PATCH 3/3] ci --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 3e5a7d6..b722be3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,7 +10,7 @@ jobs: Test: strategy: matrix: - go-version: [1.18.x, 1.20.x, 1.21.x, 1.22.x] + go-version: [1.18.x, 1.20.x, 1.21.x] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: