Skip to content

Commit 784a1d1

Browse files
authored
Merge pull request #7 from openconfig/table-output
Tablefy Output and Add Demo CI Workflow
2 parents a63cca1 + 4415005 commit 784a1d1

File tree

11 files changed

+196
-74
lines changed

11 files changed

+196
-74
lines changed

.github/workflows/demo.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Demo on openconfig/public
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
7+
jobs:
8+
9+
pattern:
10+
name: pattern statement
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
15+
steps:
16+
- name: Check out code
17+
uses: actions/checkout@v2
18+
19+
- name: Setup Python
20+
uses: actions/setup-python@v2
21+
with:
22+
python-version: '3.x'
23+
24+
- name: Set up pyang
25+
run: pip3 install pyang
26+
27+
- name: Get public repo
28+
run: git clone https://github.com/openconfig/public.git ~/tmp/public
29+
30+
- name: Demo output on openconfig/public
31+
continue-on-error: true
32+
run: |
33+
OCDIR=~/tmp/public pytests/pattern_test.sh
34+
35+
posix-pattern:
36+
name: posix-pattern statement
37+
runs-on: ubuntu-latest
38+
strategy:
39+
fail-fast: false
40+
41+
steps:
42+
- name: Set up Go 1.x
43+
uses: actions/setup-go@v2
44+
# Use default go version so that we don't have to update it every time a new one comes out.
45+
id: go
46+
47+
- name: Check out code into the Go module directory
48+
uses: actions/checkout@v2
49+
50+
- name: Get dependencies
51+
run: |
52+
go get -v -t -d ./...
53+
54+
- name: Build
55+
run: go build -v ./...
56+
57+
- name: Get public repo
58+
run: git clone https://github.com/openconfig/public.git ~/tmp/public
59+
60+
- name: Demo output on openconfig/public
61+
continue-on-error: true
62+
run: |
63+
go run gotests/main.go -model-root ~/tmp/public testdata/regexp-test.yang

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
# Pattern Statement Tests for OpenConfig YANG models
22

3-
**Under implementation.**
4-
53
Tests [pattern statement](https://tools.ietf.org/html/rfc7950#section-9.4.5)
64
using a [pyang](https://github.com/mbj4668/pyang) plugin and
75
[oc-ext:posix-pattern](https://github.com/openconfig/public/blob/master/release/models/openconfig-extensions.yang#L114)
86
using [goyang](https://github.com/openconfig/goyang).
97

8+
## Demo CI Workflow
9+
10+
There is a demo CI workflow that runs on Pull Requests. They are used to demo
11+
the result of running the tests on the current YANG models. If they cause an
12+
expected test failure (perhaps because you added an uncaught corner case), it
13+
will not block merge and a minor version increment will be given.
14+
15+
## Releases
16+
17+
Releases are synchronized with the current OpenConfig YANG models. If new
18+
breaking tests are added (e.g. test cases handled incorrectly by a current
19+
pattern regex), then the minor version must be incremented.
20+
21+
At this time, major version updates are not anticipated, but could occur as a
22+
result of major changes to the repository.
23+
1024
--------------------------------------------------------------------------------
1125

1226
[OpenConfig YANG models](https://github.com/openconfig/public/blob/master/README.md)

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ go 1.15
44

55
require (
66
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
7-
github.com/openconfig/gnmi v0.0.0-20201217212801-57b8e7af2d36
7+
github.com/google/go-cmp v0.5.0
8+
github.com/openconfig/gnmi v0.0.0-20201217212801-57b8e7af2d36 // indirect
89
github.com/openconfig/goyang v0.2.4
910
github.com/openconfig/ygot v0.9.0
1011
)

gotests/main.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ package main
1919

2020
import (
2121
"flag"
22+
"fmt"
23+
"os"
2224

2325
log "github.com/golang/glog"
2426
"github.com/openconfig/pattern-regex-tests/gotests/patterncheck"
25-
"github.com/openconfig/ygot/util"
2627
)
2728

2829
var (
@@ -32,17 +33,25 @@ var (
3233
func main() {
3334
flag.Parse()
3435

36+
code := 0
37+
defer func() {
38+
os.Exit(code)
39+
}()
40+
3541
if *modelRoot == "" {
3642
log.Error("Must supply model-root path")
3743
}
3844

39-
if err := patterncheck.CheckRegexps(flag.Args(), []string{*modelRoot}); err != nil {
40-
if errors, ok := err.(util.Errors); ok {
41-
for _, err := range errors {
42-
log.Errorln(err)
43-
}
44-
} else {
45-
log.Exit(err)
45+
failureMessages, err := patterncheck.CheckRegexps(flag.Args(), []string{*modelRoot})
46+
if err != nil {
47+
log.Exit(err)
48+
}
49+
if len(failureMessages) != 0 {
50+
code = 1
51+
fmt.Fprintln(os.Stderr, "| leaf | typedef | error |")
52+
fmt.Fprintln(os.Stderr, "| --- | --- | --- |")
53+
for _, msg := range failureMessages {
54+
fmt.Fprintln(os.Stderr, msg)
4655
}
4756
}
4857
}

gotests/patterncheck/patterncheck.go

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import (
2626
)
2727

2828
var (
29-
passCaseExt = regexp.MustCompile(`\w:pattern-test-pass`)
30-
failCaseExt = regexp.MustCompile(`\w:pattern-test-fail`)
29+
passCaseExt = regexp.MustCompile(`\w+:pattern-test-pass`)
30+
failCaseExt = regexp.MustCompile(`\w+:pattern-test-fail`)
3131
)
3232

3333
// YANGLeaf is a structure used to describe a particular leaf of YANG schema.
@@ -45,39 +45,42 @@ type RegexpTest struct {
4545
// CheckRegexps tests mock input data against a set of leaves that have pattern
4646
// test cases specified for them. It ensures that the regexp compiles as a
4747
// POSIX regular expression according to the OpenConfig style guide.
48-
func CheckRegexps(yangfiles, paths []string) error {
48+
func CheckRegexps(yangfiles, paths []string) ([]string, error) {
4949
yangE, errs := yangutil.ProcessModules(yangfiles, paths)
5050
if len(errs) != 0 {
51-
return fmt.Errorf("could not parse modules: %v", errs)
51+
return nil, fmt.Errorf("could not parse modules: %v", errs)
5252
}
5353
if len(yangE) == 0 {
54-
return fmt.Errorf("did not parse any modules")
54+
return nil, fmt.Errorf("did not parse any modules")
5555
}
5656

57-
var errs2 util.Errors
57+
var patternErrs util.Errors
58+
var allFailMessages []string
5859
for _, mod := range yangE {
5960
for _, entry := range mod.Dir {
60-
if err := checkEntryPatterns(entry); err != nil {
61-
errs2 = util.AppendErr(errs2, err)
61+
if failMessages, err := checkEntryPatterns(entry); err != nil {
62+
patternErrs = util.AppendErr(patternErrs, err)
63+
} else {
64+
allFailMessages = append(allFailMessages, failMessages...)
6265
}
6366
}
6467
}
65-
if len(errs2) == 0 {
66-
return nil
68+
if len(patternErrs) != 0 {
69+
return nil, patternErrs
6770
}
68-
return errs2
71+
return allFailMessages, nil
6972
}
7073

71-
func checkEntryPatterns(entry *yang.Entry) error {
74+
func checkEntryPatterns(entry *yang.Entry) ([]string, error) {
7275
if entry.Kind != yang.LeafEntry {
73-
return nil
76+
return nil, nil
7477
}
7578

7679
if len(entry.Errors) != 0 {
77-
return fmt.Errorf("entry had associated errors: %v", entry.Errors)
80+
return nil, fmt.Errorf("entry had associated errors: %v", entry.Errors)
7881
}
7982

80-
var errs util.Errors
83+
var failMessages []string
8184
for _, ext := range entry.Exts {
8285
var wantMatch bool
8386
switch {
@@ -93,7 +96,7 @@ func checkEntryPatterns(entry *yang.Entry) error {
9396
if len(entry.Type.Type) == 0 {
9497
var err error
9598
if gotMatch, err = checkPatterns(ext.Argument, entry.Type.POSIXPattern); err != nil {
96-
return err
99+
return nil, err
97100
}
98101
} else {
99102
// Handle unions.
@@ -107,26 +110,23 @@ func checkEntryPatterns(entry *yang.Entry) error {
107110
}
108111
matches, err := checkPatterns(ext.Argument, membertype.POSIXPattern)
109112
if err != nil {
110-
return err
113+
return nil, err
111114
}
112115
gotMatch = gotMatch || matches
113116
}
114117
}
115118

116-
matchDesc := fmt.Sprintf("%q doesn't match type %s (leaf %s)", ext.Argument, entry.Type.Name, entry.Name)
117-
if gotMatch {
118-
matchDesc = fmt.Sprintf("%q matches type %s (leaf %s)", ext.Argument, entry.Type.Name, entry.Name)
119+
matchDesc := fmt.Sprintf("| `%s` | `%s` | `%s` matched but shouldn't |", entry.Name, entry.Type.Name, ext.Argument)
120+
if !gotMatch {
121+
matchDesc = fmt.Sprintf("| `%s` | `%s` | `%s` did not match |", entry.Name, entry.Type.Name, ext.Argument)
119122
}
120123

121124
if gotMatch != wantMatch {
122-
errs = util.AppendErr(errs, fmt.Errorf("fail: %s", matchDesc))
125+
failMessages = append(failMessages, matchDesc)
123126
}
124127
log.Infof("pass: %s", matchDesc)
125128
}
126-
if len(errs) == 0 {
127-
return nil
128-
}
129-
return errs
129+
return failMessages, nil
130130
}
131131

132132
// checkPatterns compiles all given POSIX patterns, and returns true if

gotests/patterncheck/patterncheck_test.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,52 @@ package patterncheck
1717
import (
1818
"testing"
1919

20-
"github.com/openconfig/gnmi/errdiff"
20+
"github.com/google/go-cmp/cmp"
2121
)
2222

2323
func TestCheckRegexps(t *testing.T) {
2424
tests := []struct {
2525
desc string
2626
inFiles []string
2727
inPaths []string
28-
wantErrSubstring string
28+
wantFailMessages []string
2929
}{{
3030
desc: "passing cases",
3131
inFiles: []string{"testdata/passing.yang"},
3232
inPaths: []string{"../../testdata"},
3333
}, {
34-
desc: "simple leaf fail",
35-
inFiles: []string{"testdata/simple-leaf-fail.yang"},
36-
inPaths: []string{"../../testdata"},
37-
wantErrSubstring: `"ipv4" matches type string (leaf ipv-0), fail: "ipv6" doesn't match type string (leaf ipv-0)`,
34+
desc: "simple leaf fail",
35+
inFiles: []string{"testdata/simple-leaf-fail.yang"},
36+
inPaths: []string{"../../testdata"},
37+
wantFailMessages: []string{
38+
"| `ipv-0` | `string` | `ipv4` matched but shouldn't |",
39+
"| `ipv-0` | `string` | `ipv6` did not match |",
40+
},
3841
}, {
39-
desc: "union leaf fail",
40-
inFiles: []string{"testdata/union-leaf-fail.yang"},
41-
inPaths: []string{"../../testdata"},
42-
wantErrSubstring: `fail: "ipv4" matches type ip-string-typedef (leaf ipv-0), fail: "ipv5" doesn't match type ip-string-typedef (leaf ipv-0)`,
42+
desc: "union leaf fail",
43+
inFiles: []string{"testdata/union-leaf-fail.yang"},
44+
inPaths: []string{"../../testdata"},
45+
wantFailMessages: []string{
46+
"| `ipv-0` | `ip-string-typedef` | `ipv4` matched but shouldn't |",
47+
"| `ipv-0` | `ip-string-typedef` | `ipv5` did not match |",
48+
},
4349
}, {
44-
desc: "derived string type fail",
45-
inFiles: []string{"testdata/derived-string-fail.yang"},
46-
inPaths: []string{"../../testdata"},
47-
wantErrSubstring: `fail: "ipV4" doesn't match type ipv4-address-str (leaf ipv-0), fail: "ipV4-address" matches type ipv4-address-str (leaf ipv-0)`,
50+
desc: "derived string type fail",
51+
inFiles: []string{"testdata/derived-string-fail.yang"},
52+
inPaths: []string{"../../testdata"},
53+
wantFailMessages: []string{
54+
"| `ipv-0` | `ipv4-address-str` | `ipV4` did not match |",
55+
"| `ipv-0` | `ipv4-address-str` | `ipV4-address` matched but shouldn't |",
56+
},
4857
}}
4958

5059
for _, tt := range tests {
5160
t.Run(tt.desc, func(t *testing.T) {
52-
got := CheckRegexps(tt.inFiles, tt.inPaths)
53-
if diff := errdiff.Substring(got, tt.wantErrSubstring); diff != "" {
61+
got, err := CheckRegexps(tt.inFiles, tt.inPaths)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
if diff := cmp.Diff(got, tt.wantFailMessages); diff != "" {
5466
t.Errorf("(-got, +want):\n%s", diff)
5567
}
5668
})

pytests/pattern_test.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,13 @@ fi
99
TEST_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
1010
REPO_DIR="$TEST_DIR/.."
1111

12-
pyang -p $OCDIR -p "$REPO_DIR/testdata" --msg-template="line {line}: {msg}" --plugindir "$REPO_DIR/pytests/plugins" --check-patterns "$REPO_DIR/testdata/regexp-test.yang"
12+
tmpstderr=$(mktemp)
13+
pyang -p $OCDIR -p "$REPO_DIR/testdata" --msg-template="| {line} | {msg} |" --plugindir "$REPO_DIR/pytests/plugins" --check-patterns "$REPO_DIR/testdata/regexp-test.yang" 2> $tmpstderr
14+
retcode=$?
15+
if [ $retcode -ne 0 ]; then
16+
>&2 echo "| Line # | typedef | error |"
17+
>&2 echo "| --- | --- | --- |"
18+
fi
19+
>&2 cat $tmpstderr
20+
rm $tmpstderr
21+
exit $retcode

pytests/plugins/pattern_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,18 @@ def setup_ctx(self, ctx):
5757
# Test case failure states.
5858
error.add_error_code(
5959
'VALID_PATTERN_DOES_NOT_MATCH', ErrorLevel.MAJOR,
60-
'type "%s" rejected valid pattern: "%s"')
60+
'`%s` | `%s` did not match')
6161
error.add_error_code(
6262
'INVALID_PATTERN_MATCH', ErrorLevel.MAJOR,
63-
'type "%s" accepted invalid pattern: "%s"')
63+
'`%s` | `%s` matched but shouldn\'t')
6464

6565
# Error states.
6666
error.add_error_code(
6767
'NO_TEST_PATTERNS', ErrorLevel.CRITICAL,
68-
'leaf "%s" does not have any test cases')
68+
'| leaf `%s` does not have any test cases')
6969
error.add_error_code(
7070
'UNRESTRICTED_TYPE', ErrorLevel.CRITICAL,
71-
'leaf "%s" has unrestricted string type')
71+
'| leaf `%s` has unrestricted string type')
7272

7373
typedef_usage_stmt_regex = re.compile(r'([^\s:]+:)?([^\s:]+)')
7474

pytests/tests/golden.txt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
testdata/python-plugin-test.yang:20: error: type "string" accepted invalid pattern: "ipv4"
2-
testdata/python-plugin-test.yang:21: error: type "string" rejected valid pattern: "ipv6"
3-
testdata/python-plugin-test.yang:23: error: leaf "ipv4-2" does not have any test cases
4-
testdata/python-plugin-test.yang:31: error: leaf "string" has unrestricted string type
5-
testdata/python-plugin-test.yang:34: error: leaf "union" has unrestricted string type
6-
testdata/python-plugin-test.yang:54: error: type "union" accepted invalid pattern: "ipv4"
7-
testdata/python-plugin-test.yang:55: error: type "union" rejected valid pattern: "ipv5"
8-
testdata/python-plugin-test.yang:64: error: type "t:ip-string" accepted invalid pattern: "ipv4"
9-
testdata/python-plugin-test.yang:65: error: type "t:ip-string" rejected valid pattern: "ipv5"
10-
testdata/python-plugin-test.yang:74: error: type "t:ip-string-typedef" accepted invalid pattern: "ipv4"
11-
testdata/python-plugin-test.yang:75: error: type "t:ip-string-typedef" rejected valid pattern: "ipv5"
12-
testdata/python-plugin-test.yang:91: error: type "union" accepted invalid pattern: "ipv4"
13-
testdata/python-plugin-test.yang:92: error: type "union" rejected valid pattern: "hehe"
14-
testdata/python-plugin-test.yang:93: error: type "union" accepted invalid pattern: "ipV5"
15-
testdata/python-plugin-test.yang:94: error: type "union" accepted invalid pattern: "ipv6"
16-
testdata/python-plugin-test.yang:104: error: type "t:ipv4-str" rejected valid pattern: "ipv6"
1+
| 20 | `string` | `ipv4` matched but shouldn't |
2+
| 21 | `string` | `ipv6` did not match |
3+
| 23 | | leaf `ipv4-2` does not have any test cases |
4+
| 31 | | leaf `string` has unrestricted string type |
5+
| 34 | | leaf `union` has unrestricted string type |
6+
| 54 | `union` | `ipv4` matched but shouldn't |
7+
| 55 | `union` | `ipv5` did not match |
8+
| 64 | `t:ip-string` | `ipv4` matched but shouldn't |
9+
| 65 | `t:ip-string` | `ipv5` did not match |
10+
| 74 | `t:ip-string-typedef` | `ipv4` matched but shouldn't |
11+
| 75 | `t:ip-string-typedef` | `ipv5` did not match |
12+
| 91 | `union` | `ipv4` matched but shouldn't |
13+
| 92 | `union` | `hehe` did not match |
14+
| 93 | `union` | `ipV5` matched but shouldn't |
15+
| 94 | `union` | `ipv6` matched but shouldn't |
16+
| 104 | `t:ipv4-str` | `ipv6` did not match |

0 commit comments

Comments
 (0)