| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package cryptotest |
| |
| import ( |
| "fmt" |
| "reflect" |
| "slices" |
| "testing" |
| ) |
| |
| // NoExtraMethods checks that the concrete type of *ms has no exported methods |
| // beyond the methods of the interface type of *ms, and any others specified in |
| // the allowed list. |
| // |
| // These methods are accessible through interface upgrades, so they end up part |
| // of the API even if undocumented per Hyrum's Law. |
| // |
| // ms must be a pointer to a non-nil interface. |
| func NoExtraMethods(t *testing.T, ms interface{}, allowed ...string) { |
| t.Helper() |
| extraMethods, err := extraMethods(ms) |
| if err != nil { |
| t.Fatal(err) |
| } |
| for _, m := range extraMethods { |
| if slices.Contains(allowed, m) { |
| continue |
| } |
| t.Errorf("unexpected method %q", m) |
| } |
| } |
| |
| func extraMethods(ip interface{}) ([]string, error) { |
| v := reflect.ValueOf(ip) |
| if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Interface || v.Elem().IsNil() { |
| return nil, fmt.Errorf("argument must be a pointer to a non-nil interface") |
| } |
| |
| interfaceType := v.Elem().Type() |
| concreteType := v.Elem().Elem().Type() |
| |
| interfaceMethods := make(map[string]bool) |
| for i := range interfaceType.NumMethod() { |
| interfaceMethods[interfaceType.Method(i).Name] = true |
| } |
| |
| var extraMethods []string |
| for i := range concreteType.NumMethod() { |
| m := concreteType.Method(i) |
| if !m.IsExported() { |
| continue |
| } |
| if !interfaceMethods[m.Name] { |
| extraMethods = append(extraMethods, m.Name) |
| } |
| } |
| |
| return extraMethods, nil |
| } |