diff --git a/autocert/autocert.go b/autocert/autocert.go index ce6fbfff..fde6a2fd 100644 --- a/autocert/autocert.go +++ b/autocert/autocert.go @@ -169,7 +169,7 @@ func (m *manager) HTTPChallengeResolver(ctx context.Context, listenAddress strin // AcquireCertificates tries to acquire the certificates for the given hostnames synchronously. func (m *manager) AcquireCertificates(ctx context.Context, hostnames []string) error { m.lock.Lock() - added, removed := slices.Diff(hostnames, m.hostnames) + added, removed := slices.DiffComparable(hostnames, m.hostnames) m.lock.Unlock() var err error @@ -202,7 +202,7 @@ func (m *manager) AcquireCertificates(ctx context.Context, hostnames []string) e // ManageCertificates is the same as AcquireCertificates but it does it in the background. func (m *manager) ManageCertificates(ctx context.Context, hostnames []string) error { m.lock.Lock() - added, removed := slices.Diff(hostnames, m.hostnames) + added, removed := slices.DiffComparable(hostnames, m.hostnames) m.hostnames = make([]string, len(hostnames)) copy(m.hostnames, hostnames) m.lock.Unlock() diff --git a/ffmpeg/skills/data.go b/ffmpeg/skills/data.go new file mode 100644 index 00000000..cf460fec --- /dev/null +++ b/ffmpeg/skills/data.go @@ -0,0 +1,57 @@ +package skills + +var ffmpegdata = `ffmpeg version 4.4.1-datarhei Copyright (c) 2000-2021 the FFmpeg developers +built with gcc 10.3.1 (Alpine 10.3.1_git20211027) 20211027 +configuration: --extra-version=datarhei --prefix=/usr --extra-libs='-lpthread -lm -lz -lsupc++ -lstdc++ -lssl -lcrypto -lz -lc -ldl' --enable-nonfree --enable-gpl --enable-version3 --enable-postproc --enable-static --enable-openssl --enable-omx --enable-omx-rpi --enable-mmal --enable-v4l2_m2m --enable-libfreetype --enable-libsrt --enable-libx264 --enable-libx265 --enable-libvpx --enable-libmp3lame --enable-libopus --enable-libvorbis --disable-ffplay --disable-debug --disable-doc --disable-shared +libavutil 56. 70.100 / 56. 70.100 +libavcodec 58.134.100 / 58.134.100 +libavformat 58. 76.100 / 58. 76.100 +libavdevice 58. 13.100 / 58. 13.100 +libavfilter 7.110.100 / 7.110.100 +libswscale 5. 9.100 / 5. 9.100 +libswresample 3. 9.100 / 3. 9.100 +libpostproc 55. 9.100 / 55. 9.100` + +var filterdata = ` ... afirsrc |->A Generate a FIR coefficients audio stream. +... anoisesrc |->A Generate a noise audio signal. +... anullsrc |->A Null audio source, return empty audio frames. +... hilbert |->A Generate a Hilbert transform FIR coefficients. +... sinc |->A Generate a sinc kaiser-windowed low-pass, high-pass, band-pass, or band-reject FIR coefficients. +... sine |->A Generate sine wave audio signal. +... anullsink A->| Do absolutely nothing with the input audio. +... addroi V->V Add region of interest to frame. +... alphaextract V->N Extract an alpha channel as a grayscale image component. +T.. alphamerge VV->V Copy the luma value of the second input into the alpha channel of the first input.` + +var codecdata = ` DEAIL. aac AAC (Advanced Audio Coding) (decoders: aac aac_fixed aac_at ) (encoders: aac aac_at ) +DEVI.S y41p Uncompressed YUV 4:1:1 12-bit +DEV.LS h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (encoders: libx264 libx264rgb h264_videotoolbox ) +DEV.L. flv1 FLV / Sorenson Spark / Sorenson H.263 (Flash Video) (decoders: flv ) (encoders: flv )` + +var formatdata = ` DE mpeg MPEG-1 Systems / MPEG program stream + E mpeg1video raw MPEG-1 video + E mpeg2video raw MPEG-2 video +DE mpegts MPEG-TS (MPEG-2 Transport Stream) +D mpegtsraw raw MPEG-TS (MPEG-2 Transport Stream) +D mpegvideo raw MPEG video` + +var protocoldata = `Input: +async +bluray +cache +Output: +crypto +file +ftp +gopher` + +var hwacceldata = `Hardware acceleration methods: +videotoolbox` + +var v4ldata = `mmal service 16.1 (platform:bcm2835-v4l2): + /dev/video0 + +Webcam C170: Webcam C170 (usb-3f980000.usb-1.3): + /dev/video1 + +` diff --git a/ffmpeg/skills/skills.go b/ffmpeg/skills/skills.go index 8f9d103f..956c0a9d 100644 --- a/ffmpeg/skills/skills.go +++ b/ffmpeg/skills/skills.go @@ -11,6 +11,8 @@ import ( "os/exec" "regexp" "strings" + + "github.com/datarhei/core/v16/slices" ) // Codec represents a codec with its availabe encoders and decoders @@ -21,12 +23,48 @@ type Codec struct { Decoders []string } +func (a Codec) Equal(b Codec) bool { + if a.Id != b.Id { + return false + } + + if a.Name != b.Name { + return false + } + + if !slices.EqualComparableElements(a.Encoders, b.Encoders) { + return false + } + + if !slices.EqualComparableElements(a.Decoders, b.Decoders) { + return false + } + + return true +} + type ffCodecs struct { Audio []Codec Video []Codec Subtitle []Codec } +func (a ffCodecs) Equal(b ffCodecs) bool { + if !slices.EqualEqualerElements(a.Audio, b.Audio) { + return false + } + + if !slices.EqualEqualerElements(a.Video, b.Video) { + return false + } + + if !slices.EqualEqualerElements(a.Subtitle, b.Subtitle) { + return false + } + + return true +} + // HWDevice represents a hardware device (e.g. USB device) type HWDevice struct { Id string @@ -35,6 +73,26 @@ type HWDevice struct { Media string } +func (a HWDevice) Equal(b HWDevice) bool { + if a.Id != b.Id { + return false + } + + if a.Name != b.Name { + return false + } + + if a.Extra != b.Extra { + return false + } + + if a.Media != b.Media { + return false + } + + return true +} + // Device represents a type of device (e.g. V4L2) including connected actual devices type Device struct { Id string @@ -42,11 +100,39 @@ type Device struct { Devices []HWDevice } +func (a Device) Equal(b Device) bool { + if a.Id != b.Id { + return false + } + + if a.Name != b.Name { + return false + } + + if !slices.EqualEqualerElements(a.Devices, b.Devices) { + return false + } + + return true +} + type ffDevices struct { Demuxers []Device Muxers []Device } +func (a ffDevices) Equal(b ffDevices) bool { + if !slices.EqualEqualerElements(a.Demuxers, b.Demuxers) { + return false + } + + if !slices.EqualEqualerElements(a.Muxers, b.Muxers) { + return false + } + + return true +} + // Format represents a supported format (e.g. flv) type Format struct { Id string @@ -58,6 +144,18 @@ type ffFormats struct { Muxers []Format } +func (a ffFormats) Equal(b ffFormats) bool { + if !slices.EqualComparableElements(a.Demuxers, b.Demuxers) { + return false + } + + if !slices.EqualComparableElements(a.Muxers, b.Muxers) { + return false + } + + return true +} + // Protocol represents a supported protocol (e.g. rtsp) type Protocol struct { Id string @@ -69,6 +167,18 @@ type ffProtocols struct { Output []Protocol } +func (a ffProtocols) Equal(b ffProtocols) bool { + if !slices.EqualComparableElements(a.Input, b.Input) { + return false + } + + if !slices.EqualComparableElements(a.Output, b.Output) { + return false + } + + return true +} + type HWAccel struct { Id string Name string @@ -94,6 +204,26 @@ type ffmpeg struct { Libraries []Library } +func (a ffmpeg) Equal(b ffmpeg) bool { + if a.Version != b.Version { + return false + } + + if a.Compiler != b.Compiler { + return false + } + + if a.Configuration != b.Configuration { + return false + } + + if !slices.EqualComparableElements(a.Libraries, b.Libraries) { + return false + } + + return true +} + // Skills are the detected capabilities of a ffmpeg binary type Skills struct { FFmpeg ffmpeg @@ -107,6 +237,38 @@ type Skills struct { Protocols ffProtocols } +func (a Skills) Equal(b Skills) bool { + if !a.FFmpeg.Equal(b.FFmpeg) { + return false + } + + if !slices.EqualComparableElements(a.Filters, b.Filters) { + return false + } + + if !slices.EqualComparableElements(a.HWAccels, b.HWAccels) { + return false + } + + if !a.Codecs.Equal(b.Codecs) { + return false + } + + if !a.Devices.Equal(b.Devices) { + return false + } + + if !a.Formats.Equal(b.Formats) { + return false + } + + if !a.Protocols.Equal(b.Protocols) { + return false + } + + return true +} + // New returns all skills that ffmpeg provides func New(binary string) (Skills, error) { c := Skills{} @@ -193,7 +355,7 @@ func filters(binary string) []Filter { func parseFilters(data []byte) []Filter { filters := []Filter{} - re := regexp.MustCompile(`^\s[TSC.]{3} ([0-9A-Za-z_]+)\s+(?:.*?)\s+(.*)?$`) + re := regexp.MustCompile(`^\s*[TSC.]{3} ([0-9A-Za-z_]+)\s+(?:.*?)\s+(.*)?$`) scanner := bufio.NewScanner(bytes.NewReader(data)) scanner.Split(bufio.ScanLines) @@ -227,7 +389,7 @@ func codecs(binary string) ffCodecs { func parseCodecs(data []byte) ffCodecs { codecs := ffCodecs{} - re := regexp.MustCompile(`^\s([D.])([E.])([VAS]).{3} ([0-9A-Za-z_]+)\s+(.*?)(?:\(decoders:([^\)]+)\))?\s?(?:\(encoders:([^\)]+)\))?$`) + re := regexp.MustCompile(`^\s*([D.])([E.])([VAS]).{3} ([0-9A-Za-z_]+)\s+(.*?)(?:\(decoders:([^\)]+)\))?\s?(?:\(encoders:([^\)]+)\))?$`) scanner := bufio.NewScanner(bytes.NewReader(data)) scanner.Split(bufio.ScanLines) @@ -286,7 +448,7 @@ func formats(binary string) ffFormats { func parseFormats(data []byte) ffFormats { formats := ffFormats{} - re := regexp.MustCompile(`^\s([D ])([E ]) ([0-9A-Za-z_,]+)\s+(.*?)$`) + re := regexp.MustCompile(`^\s*([D ])([E ])\s+([0-9A-Za-z_,]+)\s+(.*?)$`) scanner := bufio.NewScanner(bytes.NewReader(data)) scanner.Split(bufio.ScanLines) @@ -330,7 +492,7 @@ func devices(binary string) ffDevices { func parseDevices(data []byte, binary string) ffDevices { devices := ffDevices{} - re := regexp.MustCompile(`^\s([D ])([E ]) ([0-9A-Za-z_,]+)\s+(.*?)$`) + re := regexp.MustCompile(`^\s*([D ])([E ]) ([0-9A-Za-z_,]+)\s+(.*?)$`) scanner := bufio.NewScanner(bytes.NewReader(data)) scanner.Split(bufio.ScanLines) diff --git a/ffmpeg/skills/skills_test.go b/ffmpeg/skills/skills_test.go index bc992a27..e4112236 100644 --- a/ffmpeg/skills/skills_test.go +++ b/ffmpeg/skills/skills_test.go @@ -1,8 +1,10 @@ package skills import ( + "bytes" "testing" + "github.com/datarhei/core/v16/slices" "github.com/stretchr/testify/require" ) @@ -33,6 +35,70 @@ func TestNewInvalidBinary(t *testing.T) { require.Empty(t, skills.Protocols.Output) } +func TestEqualEmptySkills(t *testing.T) { + s := Skills{} + + ok := s.Equal(s) + require.True(t, ok) +} + +func TestEuqalSkills(t *testing.T) { + s1 := Skills{ + FFmpeg: parseVersion([]byte(ffmpegdata)), + Filters: parseFilters([]byte(filterdata)), + HWAccels: parseHWAccels([]byte(hwacceldata)), + Codecs: parseCodecs([]byte(codecdata)), + Devices: ffDevices{}, + Formats: parseFormats([]byte(formatdata)), + Protocols: parseProtocols([]byte(protocoldata)), + } + + devices := parseV4LDevices(bytes.NewBuffer(slices.Copy([]byte(v4ldata)))) + + s1.Devices.Demuxers = append(s1.Devices.Demuxers, Device{ + Id: "v4l2", + Name: "webcam", + Devices: devices, + }) + s1.Devices.Muxers = append(s1.Devices.Muxers, Device{ + Id: "v4l2", + Name: "webcam", + Devices: devices, + }) + + ok := s1.Equal(s1) + require.True(t, ok) + + s2 := Skills{ + FFmpeg: parseVersion([]byte(ffmpegdata)), + Filters: parseFilters([]byte(filterdata)), + HWAccels: parseHWAccels([]byte(hwacceldata)), + Codecs: parseCodecs([]byte(codecdata)), + Devices: ffDevices{}, + Formats: parseFormats([]byte(formatdata)), + Protocols: parseProtocols([]byte(protocoldata)), + } + + devices = parseV4LDevices(bytes.NewBuffer(slices.Copy([]byte(v4ldata)))) + + s2.Devices.Demuxers = append(s2.Devices.Demuxers, Device{ + Id: "v4l2", + Name: "webcam", + Devices: devices, + }) + s2.Devices.Muxers = append(s2.Devices.Muxers, Device{ + Id: "v4l2", + Name: "webcam", + Devices: devices, + }) + + ok = s1.Equal(s2) + require.True(t, ok) + + ok = s1.Equal(Skills{}) + require.False(t, ok) +} + func TestPatchVersion(t *testing.T) { data := `ffmpeg version 4.3.1 Copyright (c) 2000-2020 the FFmpeg developers built with Apple clang version 12.0.0 (clang-1200.0.32.29) @@ -162,17 +228,7 @@ func TestMinorVersion(t *testing.T) { } func TestCustomVersion(t *testing.T) { - data := `ffmpeg version 4.4.1-datarhei Copyright (c) 2000-2021 the FFmpeg developers -built with gcc 10.3.1 (Alpine 10.3.1_git20211027) 20211027 -configuration: --extra-version=datarhei --prefix=/usr --extra-libs='-lpthread -lm -lz -lsupc++ -lstdc++ -lssl -lcrypto -lz -lc -ldl' --enable-nonfree --enable-gpl --enable-version3 --enable-postproc --enable-static --enable-openssl --enable-omx --enable-omx-rpi --enable-mmal --enable-v4l2_m2m --enable-libfreetype --enable-libsrt --enable-libx264 --enable-libx265 --enable-libvpx --enable-libmp3lame --enable-libopus --enable-libvorbis --disable-ffplay --disable-debug --disable-doc --disable-shared -libavutil 56. 70.100 / 56. 70.100 -libavcodec 58.134.100 / 58.134.100 -libavformat 58. 76.100 / 58. 76.100 -libavdevice 58. 13.100 / 58. 13.100 -libavfilter 7.110.100 / 7.110.100 -libswscale 5. 9.100 / 5. 9.100 -libswresample 3. 9.100 / 3. 9.100 -libpostproc 55. 9.100 / 55. 9.100` + data := ffmpegdata f := parseVersion([]byte(data)) @@ -226,16 +282,7 @@ libpostproc 55. 9.100 / 55. 9.100` } func TestFilters(t *testing.T) { - data := ` ... afirsrc |->A Generate a FIR coefficients audio stream. - ... anoisesrc |->A Generate a noise audio signal. - ... anullsrc |->A Null audio source, return empty audio frames. - ... hilbert |->A Generate a Hilbert transform FIR coefficients. - ... sinc |->A Generate a sinc kaiser-windowed low-pass, high-pass, band-pass, or band-reject FIR coefficients. - ... sine |->A Generate sine wave audio signal. - ... anullsink A->| Do absolutely nothing with the input audio. - ... addroi V->V Add region of interest to frame. - ... alphaextract V->N Extract an alpha channel as a grayscale image component. - T.. alphamerge VV->V Copy the luma value of the second input into the alpha channel of the first input.` + data := filterdata f := parseFilters([]byte(data)) @@ -284,10 +331,7 @@ func TestFilters(t *testing.T) { } func TestCodecs(t *testing.T) { - data := ` DEAIL. aac AAC (Advanced Audio Coding) (decoders: aac aac_fixed aac_at ) (encoders: aac aac_at ) - DEVI.S y41p Uncompressed YUV 4:1:1 12-bit - DEV.LS h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (encoders: libx264 libx264rgb h264_videotoolbox ) - DEV.L. flv1 FLV / Sorenson Spark / Sorenson H.263 (Flash Video) (decoders: flv ) (encoders: flv )` + data := codecdata c := parseCodecs([]byte(data)) @@ -346,12 +390,7 @@ func TestCodecs(t *testing.T) { } func TestFormats(t *testing.T) { - data := ` DE mpeg MPEG-1 Systems / MPEG program stream - E mpeg1video raw MPEG-1 video - E mpeg2video raw MPEG-2 video - DE mpegts MPEG-TS (MPEG-2 Transport Stream) - D mpegtsraw raw MPEG-TS (MPEG-2 Transport Stream) - D mpegvideo raw MPEG video` + data := formatdata f := parseFormats([]byte(data)) @@ -396,15 +435,7 @@ func TestFormats(t *testing.T) { } func TestProtocols(t *testing.T) { - data := `Input: - async - bluray - cache -Output: - crypto - file - ftp - gopher` + data := protocoldata p := parseProtocols([]byte(data)) @@ -445,8 +476,7 @@ Output: } func TestHWAccels(t *testing.T) { - data := `Hardware acceleration methods: -videotoolbox` + data := hwacceldata p := parseHWAccels([]byte(data)) diff --git a/ffmpeg/skills/v4l2_test.go b/ffmpeg/skills/v4l2_test.go index edb81fcf..7464937c 100644 --- a/ffmpeg/skills/v4l2_test.go +++ b/ffmpeg/skills/v4l2_test.go @@ -4,6 +4,7 @@ import ( "bytes" "testing" + "github.com/datarhei/core/v16/slices" "github.com/stretchr/testify/require" ) @@ -16,15 +17,9 @@ func TestNoV4LDevices(t *testing.T) { } func TestV4LDevices(t *testing.T) { - data := bytes.NewBufferString(`mmal service 16.1 (platform:bcm2835-v4l2): - /dev/video0 + data := v4ldata -Webcam C170: Webcam C170 (usb-3f980000.usb-1.3): - /dev/video1 - -`) - - devices := parseV4LDevices(data) + devices := parseV4LDevices(bytes.NewBuffer(slices.Copy([]byte(data)))) require.Equal(t, []HWDevice{ { diff --git a/slices/copy.go b/slices/copy.go index 3a46a771..20c631bb 100644 --- a/slices/copy.go +++ b/slices/copy.go @@ -6,3 +6,17 @@ func Copy[T any](src []T) []T { return dst } + +type Cloner[T any] interface { + Clone() T +} + +func CopyDeep[T any, X Cloner[T]](src []X) []T { + dst := make([]T, len(src)) + + for i, c := range src { + dst[i] = c.Clone() + } + + return dst +} diff --git a/slices/copy_test.go b/slices/copy_test.go index ebc8a448..e2731f7c 100644 --- a/slices/copy_test.go +++ b/slices/copy_test.go @@ -13,3 +13,15 @@ func TestCopy(t *testing.T) { require.Equal(t, []string{"a", "b", "c"}, b) } + +func (a String) Clone() String { + return String(string(a)) +} + +func TestCopyDeep(t *testing.T) { + a := []String{"a", "b", "c"} + + b := CopyDeep[String](a) + + require.Equal(t, []String{"a", "b", "c"}, b) +} diff --git a/slices/diff.go b/slices/diff.go index 64a499c1..4d271571 100644 --- a/slices/diff.go +++ b/slices/diff.go @@ -1,28 +1,81 @@ package slices -// Diff returns a sliceof newly added entries and a slice of removed entries based -// the provided slices. -func Diff[T comparable](next, current []T) ([]T, []T) { - added, removed := []T{}, []T{} +// DiffComparable diffs two arrays/slices and returns slices of elements that are only in A and only in B. +// If some element is present multiple times, each instance is counted separately (e.g. if something is 2x in A and +// 5x in B, it will be 0x in extraA and 3x in extraB). The order of items in both lists is ignored. +// Adapted from https://github.com/stretchr/testify/blob/f97607b89807936ac4ff96748d766cf4b9711f78/assert/assertions.go#L1073C21-L1073C21 +func DiffComparable[T comparable](listA, listB []T) ([]T, []T) { + extraA, extraB := []T{}, []T{} - currentMap := map[T]struct{}{} + aLen := len(listA) + bLen := len(listB) - for _, name := range current { - currentMap[name] = struct{}{} + // Mark indexes in listA that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := listA[i] + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if listB[j] == element { + visited[j] = true + found = true + break + } + } + if !found { + extraA = append(extraA, element) + } } - for _, name := range next { - if _, ok := currentMap[name]; ok { - delete(currentMap, name) + for j := 0; j < bLen; j++ { + if visited[j] { continue } - - added = append(added, name) + extraB = append(extraB, listB[j]) } - for name := range currentMap { - removed = append(removed, name) - } - - return added, removed + return extraA, extraB +} + +// DiffEqualer diffs two arrays/slices where each element implements the Equaler interface and returns slices of +// elements that are only in A and only in B. If some element is present multiple times, each instance is counted +// separately (e.g. if something is 2x in A and 5x in B, it will be 0x in extraA and 3x in extraB). The order of +// items in both lists is ignored. +func DiffEqualer[T any, X Equaler[T]](listA []T, listB []X) ([]T, []X) { + extraA, extraB := []T{}, []X{} + + aLen := len(listA) + bLen := len(listB) + + // Mark indexes in listA that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := listA[i] + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if listB[j].Equal(element) { + visited[j] = true + found = true + break + } + } + if !found { + extraA = append(extraA, element) + } + } + + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + extraB = append(extraB, listB[j]) + } + + return extraA, extraB } diff --git a/slices/diff_test.go b/slices/diff_test.go index 9f66773e..e1b1891d 100644 --- a/slices/diff_test.go +++ b/slices/diff_test.go @@ -6,12 +6,22 @@ import ( "github.com/stretchr/testify/require" ) -func TestDiff(t *testing.T) { +func TestDiffComparable(t *testing.T) { a := []string{"c", "d", "e", "f"} - b := []string{"a", "b", "c", "d"} + b := []string{"a", "a", "b", "c", "d"} - added, removed := Diff(a, b) + added, removed := DiffComparable(a, b) require.ElementsMatch(t, []string{"e", "f"}, added) - require.ElementsMatch(t, []string{"a", "b"}, removed) + require.ElementsMatch(t, []string{"a", "a", "b"}, removed) +} + +func TestDiffEqualer(t *testing.T) { + a := []String{"c", "d", "e", "f"} + b := []String{"a", "a", "b", "c", "d"} + + added, removed := DiffComparable(a, b) + + require.ElementsMatch(t, []String{"e", "f"}, added) + require.ElementsMatch(t, []String{"a", "a", "b"}, removed) } diff --git a/slices/equal.go b/slices/equal.go new file mode 100644 index 00000000..29ed6468 --- /dev/null +++ b/slices/equal.go @@ -0,0 +1,28 @@ +package slices + +// EqualComparableElements returns whether two slices have the same elements. +func EqualComparableElements[T comparable](a, b []T) bool { + extraA, extraB := DiffComparable(a, b) + + if len(extraA) == 0 && len(extraB) == 0 { + return true + } + + return false +} + +// Equaler defines a type that implements the Equal function. +type Equaler[T any] interface { + Equal(T) bool +} + +// EqualEqualerElements returns whether two slices of Equaler have the same elements. +func EqualEqualerElements[T any, X Equaler[T]](a []T, b []X) bool { + extraA, extraB := DiffEqualer(a, b) + + if len(extraA) == 0 && len(extraB) == 0 { + return true + } + + return false +} diff --git a/slices/equal_test.go b/slices/equal_test.go new file mode 100644 index 00000000..7769f843 --- /dev/null +++ b/slices/equal_test.go @@ -0,0 +1,51 @@ +package slices + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEqualComparableElements(t *testing.T) { + a := []string{"a", "b", "c", "d"} + b := []string{"b", "c", "a", "d"} + + ok := EqualComparableElements(a, b) + require.True(t, ok) + + ok = EqualComparableElements(b, a) + require.True(t, ok) + + a = append(a, "z") + + ok = EqualComparableElements(a, b) + require.False(t, ok) + + ok = EqualComparableElements(b, a) + require.False(t, ok) +} + +type String string + +func (a String) Equal(b String) bool { + return string(a) == string(b) +} + +func TestEqualEqualerElements(t *testing.T) { + a := []String{"a", "b", "c", "d"} + b := []String{"b", "c", "a", "d"} + + ok := EqualEqualerElements(a, b) + require.True(t, ok) + + ok = EqualEqualerElements(b, a) + require.True(t, ok) + + a = append(a, "z") + + ok = EqualEqualerElements(a, b) + require.False(t, ok) + + ok = EqualEqualerElements(b, a) + require.False(t, ok) +}