From d78f2ad4b32d119ce8cb907e1aeb41396fed2cf9 Mon Sep 17 00:00:00 2001 From: Avery Carty Date: Thu, 4 Mar 2021 18:14:35 -0500 Subject: [PATCH] lang: funcs: Testing some simple functions and mcl --- examples/lang/contains1.mcl | 16 ++++ examples/lang/ipport.mcl | 12 +++ examples/lang/string_counter.mcl | 14 ++++ lang/funcs/core/net/ip_port.go | 58 ++++++++++++++ lang/funcs/core/net/ip_port_test.go | 76 +++++++++++++++++++ lang/funcs/core/strings/contains_func.go | 39 ++++++++++ lang/funcs/core/strings/contains_func_test.go | 65 ++++++++++++++++ lang/funcs/core/strings/count_func.go | 49 ++++++++++++ lang/funcs/core/strings/count_func_test.go | 69 +++++++++++++++++ 9 files changed, 398 insertions(+) create mode 100644 examples/lang/contains1.mcl create mode 100644 examples/lang/ipport.mcl create mode 100644 examples/lang/string_counter.mcl create mode 100644 lang/funcs/core/net/ip_port.go create mode 100644 lang/funcs/core/net/ip_port_test.go create mode 100644 lang/funcs/core/strings/contains_func.go create mode 100644 lang/funcs/core/strings/contains_func_test.go create mode 100644 lang/funcs/core/strings/count_func.go create mode 100644 lang/funcs/core/strings/count_func_test.go diff --git a/examples/lang/contains1.mcl b/examples/lang/contains1.mcl new file mode 100644 index 000000000..8fffff8ac --- /dev/null +++ b/examples/lang/contains1.mcl @@ -0,0 +1,16 @@ +import "fmt" +import "os" +import "strings" + +$s = os.readfile("/etc/hosts") +$substr = "127.0.0.1" +$b = strings.contains($s, $substr) + +print "print1" { + msg => $b, +} + +file "/tmp/mgmt/checklist" { + state => $const.res.file.state.exists, + content => fmt.printf("/etc/hosts %s: %s", $substr, $b), +} diff --git a/examples/lang/ipport.mcl b/examples/lang/ipport.mcl new file mode 100644 index 000000000..846b6b773 --- /dev/null +++ b/examples/lang/ipport.mcl @@ -0,0 +1,12 @@ +import "net" + +$ipport = net.ip_port("192.168.1.1", "433") + +print "ipport" { + msg => $ipport, +} + +file "/tmp/mgmt/ipport" { + state => $const.res.file.state.exists, + content => $ipport, +} diff --git a/examples/lang/string_counter.mcl b/examples/lang/string_counter.mcl new file mode 100644 index 000000000..81062926a --- /dev/null +++ b/examples/lang/string_counter.mcl @@ -0,0 +1,14 @@ +import "fmt" +import "strings" +import "sys" +import "os" + +$home = sys.getenv("HOME") +$file = "${home}/.bashrc" +$s = os.readfile($file) +$substr = "" +$lenoffile = strings.count($s, $substr) + +print "print0" { + msg => fmt.printf("char length of file: %s: %s", $file, $lenoffileb, +} diff --git a/lang/funcs/core/net/ip_port.go b/lang/funcs/core/net/ip_port.go new file mode 100644 index 000000000..9fdabcfb8 --- /dev/null +++ b/lang/funcs/core/net/ip_port.go @@ -0,0 +1,58 @@ +// Mgmt +// Copyright (C) 2013-2021+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corenet + +import ( + "errors" + "fmt" + "net" + "strconv" + + "github.com/purpleidea/mgmt/lang/funcs/simple" + "github.com/purpleidea/mgmt/lang/types" +) + +func init() { + simple.ModuleRegister(ModuleName, "ip_port", &types.FuncValue{ + T: types.NewType("func(ip str, port str) str"), + V: IPPort, + }) +} + +// IPPort returns the combind IPv4/IPv6:(input[0]) and Port:(input[1]). Returns +// error if IPv4/IPv6 string is incorrect format or Port not in range 0-65536. +func IPPort(input []types.Value) (types.Value, error) { + ip := net.ParseIP(input[0].Str()) // Is nil if incorrect format. + ipStr := input[0].Str() + port := input[1].Str() + portInt, err := strconv.Atoi(port) + + if ip == nil { + return &types.StrValue{V: ""}, errors.New("incorrect ip format") + } + if err != nil { + return &types.StrValue{V: ""}, fmt.Errorf("err converting str to int %v", err) + } + if portInt < 0 || portInt > 65536 { + return &types.StrValue{V: ""}, errors.New("port not in range 0-65536") + } + + return &types.StrValue{ + V: ipStr + ":" + port, + }, nil +} diff --git a/lang/funcs/core/net/ip_port_test.go b/lang/funcs/core/net/ip_port_test.go new file mode 100644 index 000000000..9d8d75eec --- /dev/null +++ b/lang/funcs/core/net/ip_port_test.go @@ -0,0 +1,76 @@ +// Mgmt +// Copyright (C) 2013-2021+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corenet + +import ( + "errors" + "testing" + + "github.com/purpleidea/mgmt/lang/types" +) + +func TestIPPort(t *testing.T) { + ipporttests := []struct { + name string + ip string + port string + expected string + err error + }{ + // Success + {"correct ipv4 and port", "192.168.1.1", "80", "192.168.1.1:80", nil}, + {"correct ipv6 and port", "2345:0425:2CA1:0000:0000:0567:5673:23b5", "8080", "2345:0425:2CA1:0000:0000:0567:5673:23b5:8080", nil}, + {"correct ipv4 and port - allow port 0", "192.168.1.1", "0", "192.168.1.1:0", nil}, + {"correct ipv6 and port - allows port 65536", "2345:0425:2CA1::0567:5673:23b5", "65536", "2345:0425:2CA1::0567:5673:23b5:65536", nil}, + // Fail + {"incorrect ipv4 - octet over 255", "392.868.11.79", "80", "", errors.New("incorrect ip format")}, + {"incorrect ipv4 - CIDR format", "10.10.10.100/8", "23", "", errors.New("incorrect ip format")}, + {"incorrect ipv4 - dots...", "172.16.10..254", "23", "", errors.New("incorrect ip format")}, + {"incorrect ipv6 - double double colon", "5678:A425:2CA1::0567::5673:23b5", "80", "", errors.New("incorrect ip format")}, + {"incorrect ipv6 - non hex chars", "M678:Z425:2CA1::05X7:T673:23b5", "1234", "", errors.New("incorrect ip format")}, + {"incorrect port - outside of range", "192.168.1.1", "65537", "", errors.New("port not in range 0-65536")}, + {"incorrect port - negative port number", "192.168.1.1", "-9483670", "", errors.New("port not in range 0-65536")}, + } + + for _, test := range ipporttests { + t.Run(test.name, func(t *testing.T) { + output, err := IPPort([]types.Value{ + &types.StrValue{V: test.ip}, + &types.StrValue{V: test.port}, + }) + expectedStr := &types.StrValue{V: test.expected} + + if test.err != nil && err.Error() != test.err.Error() { + t.Errorf("ip: %s, port %s, expected error: %q, got %q", test.ip, test.port, test.err, err) + return + } else if test.err != nil && err == nil { + t.Errorf("ip: %s, port: %s, expected error: %v, but got nil", test.ip, test.port, test.err) + return + } else if test.err == nil && err != nil { + t.Errorf("ip: %s, port %s, did not expect error but got: %#v", test.ip, test.port, err) + return + } + if err1 := output.Cmp(expectedStr); err1 != nil { + t.Errorf("ip: %s, port: %s, expected: %s, got: %s", test.ip, test.port, expectedStr, output) + return + } + + }) + } + +} diff --git a/lang/funcs/core/strings/contains_func.go b/lang/funcs/core/strings/contains_func.go new file mode 100644 index 000000000..7104fe86e --- /dev/null +++ b/lang/funcs/core/strings/contains_func.go @@ -0,0 +1,39 @@ +// Mgmt +// Copyright (C) 2013-2021+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corestrings + +import ( + "strconv" + "strings" + + "github.com/purpleidea/mgmt/lang/funcs/simple" + "github.com/purpleidea/mgmt/lang/types" +) + +func init() { + simple.ModuleRegister(ModuleName, "contains", &types.FuncValue{ + T: types.NewType("func(s str, substr str) str"), + V: Contains, + }) +} + +// Contains reports whether substr is within s. +func Contains(input []types.Value) (types.Value, error) { + s, substr := input[0].Str(), input[1].Str() + return &types.StrValue{V: strconv.FormatBool(strings.Contains(s, substr))}, nil +} diff --git a/lang/funcs/core/strings/contains_func_test.go b/lang/funcs/core/strings/contains_func_test.go new file mode 100644 index 000000000..04f7107b0 --- /dev/null +++ b/lang/funcs/core/strings/contains_func_test.go @@ -0,0 +1,65 @@ +// Mgmt +// Copyright (C) 2013-2021+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corestrings + +import ( + "testing" + + "github.com/purpleidea/mgmt/lang/types" +) + +func TestContains(t *testing.T) { + containsTests := []struct { + name string + s string + substr string + expected string + err error + }{ + // Success + {"does contain", "tomorrow", "row", "true", nil}, + {"does not contain", "fighter", "light", "false", nil}, + } + + for _, test := range containsTests { + t.Run(test.name, func(t *testing.T) { + output, err := Contains([]types.Value{ + &types.StrValue{V: test.s}, + &types.StrValue{V: test.substr}, + }) + expectedStr := &types.StrValue{V: test.expected} + + if test.err != nil && err.Error() != test.err.Error() { + t.Errorf("s: %s, substr %s, expected error: %q, got %q", test.s, test.substr, test.err, err) + return + } else if test.err != nil && err == nil { + t.Errorf("s: %s, substr: %s, expected error: %v, but got nil", test.s, test.substr, test.err) + return + } else if test.err == nil && err != nil { + t.Errorf("s: %s, substr %s, did not expect error but got: %#v", test.s, test.substr, err) + return + } + if err1 := output.Cmp(expectedStr); err1 != nil { + t.Errorf("s: %s, substr: %s, expected: %s, got: %s", test.s, test.substr, expectedStr, output) + return + } + + }) + } + +} diff --git a/lang/funcs/core/strings/count_func.go b/lang/funcs/core/strings/count_func.go new file mode 100644 index 000000000..992e0b692 --- /dev/null +++ b/lang/funcs/core/strings/count_func.go @@ -0,0 +1,49 @@ +// Mgmt +// Copyright (C) 2013-2021+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corestrings + +import ( + "fmt" + "strings" + + "github.com/purpleidea/mgmt/lang/funcs/simple" + "github.com/purpleidea/mgmt/lang/types" +) + +func init() { + simple.ModuleRegister(ModuleName, "count", &types.FuncValue{ + T: types.NewType("func(s str, substr str) str"), + V: Count, + }) +} + +// Count counts the number of non-overlapping instances of substr in s. If +// substr is an empty string, Count returns 0 if empty string and len(string) if +// substr is empty +func Count(input []types.Value) (types.Value, error) { + s, substr := input[0].Str(), input[1].Str() + if s == "" { + return &types.StrValue{V: "0"}, nil + } + + count := strings.Count(s, substr) + if substr == "" { + count-- + } + return &types.StrValue{V: fmt.Sprint(count)}, nil +} diff --git a/lang/funcs/core/strings/count_func_test.go b/lang/funcs/core/strings/count_func_test.go new file mode 100644 index 000000000..439c0858f --- /dev/null +++ b/lang/funcs/core/strings/count_func_test.go @@ -0,0 +1,69 @@ +// Mgmt +// Copyright (C) 2013-2021+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corestrings + +import ( + "testing" + + "github.com/purpleidea/mgmt/lang/types" +) + +func TestCount(t *testing.T) { + countTests := []struct { + name string + s string + substr string + expected string + err error + }{ + // Success + {"with many results", "A copy is just an identical image. There is the possibility that a single virus could destroy an entire set of systems and copies do not give rise to variety and originality. Life perpetuates itself through diversity and this includes the ability to sacrifice itself when necessary. Cells repeat the process of degeneration and regeneration until one day they die, obliterating an entire set of memory and information. Only genes remain. Why continually repeat this cycle? Simply to survive by avoiding the weaknesses of an unchanging system.", "to", "3", nil}, + {"with 1 result", "hello world, hi world, howdy world", "hello", "1", nil}, + {"with no matches", "hello world, whats good?", "yo", "0", nil}, + {"counts substrings", "once upon a time", "on", "2", nil}, + {"with blank substring, returns len(str)", "123456789", "", "9", nil}, + {"with blank string, returns 0", "", "", "0", nil}, + } + + for _, test := range countTests { + t.Run(test.name, func(t *testing.T) { + output, err := Count([]types.Value{ + &types.StrValue{V: test.s}, + &types.StrValue{V: test.substr}, + }) + expectedStr := &types.StrValue{V: test.expected} + + if test.err != nil && err.Error() != test.err.Error() { + t.Errorf("s: %s, substr %s, expected error: %q, got %q", test.s, test.substr, test.err, err) + return + } else if test.err != nil && err == nil { + t.Errorf("s: %s, substr: %s, expected error: %v, but got nil", test.s, test.substr, test.err) + return + } else if test.err == nil && err != nil { + t.Errorf("s: %s, substr %s, did not expect error but got: %#v", test.s, test.substr, err) + return + } + if err1 := output.Cmp(expectedStr); err1 != nil { + t.Errorf("s: %s, substr: %s, expected: %s, got: %s", test.s, test.substr, expectedStr, output) + return + } + + }) + } + +}