package erlang import ( "bytes" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_parseErlang(t *testing.T) { tests := []struct { name string content string wantErr require.ErrorAssertionFunc }{ { name: "basic valid content", content: ` {"1.2.0", [{<<"bcrypt">>,{pkg,<<"bcrypt">>,<<"1.1.5">>},0}, {<<"bson">>, {git,"https://github.com/comtihon/bson-erlang", {ref,"14308ab927cfa69324742c3de720578094e0bb19"}}, 1}, {<<"syslog">>,{pkg,<<"syslog">>,<<"1.1.0">>},0}, {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}, {<<"vernemq_dev">>, {git,"https://github.com/vernemq/vernemq_dev.git", {ref,"6d622aa8c901ae7777433aef2bd049e380c474a6"}}, 0}] }. [ {pkg_hash,[ {<<"bcrypt">>, <<"A6763BD4E1AF46D34776F85B7995E63A02978DE110C077E9570ED17006E03386">>}, {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, {pkg_hash_ext,[ {<<"bcrypt">>, <<"3418821BC17CE6E96A4A77D1A88D7485BF783E212069FACFC79510AFBFF95352">>}, {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} ].`, }, { name: "empty list", content: ` {test, [ {with_space, [ ]}, {without_space, []} ]}`, }, { name: "valid strings", content: ` {strings, [ "foo", 'bar' ]}`, }, { name: "invalid string content", wantErr: require.Error, content: ` {"1.2.0 ">>}, ].`, }, { name: "string mismach", wantErr: require.Error, content: ` {bad_string, [ 'foo" ]}`, }, { name: "invalid content", wantErr: require.Error, content: ` {"1.2.0"}. ].`, }, { name: "valid comments", content: ` { comments, [ { foo, bar }, %% this is a comment % this is also a comment { hello, 'bar' }, %%inline comment { baz } ]}`, }, { name: "starts with a comments", content: ` %% starts with comment { comments, [ { foo, bar } ]}`, }, { name: "string with an escaped quote", content: ` {escaped, ["a\"b"]}`, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { value, err := parseErlang(bytes.NewReader([]byte(test.content))) if test.wantErr == nil { require.NoError(t, err) } else { test.wantErr(t, err) } assert.IsType(t, erlangNode{}, value) }) } } func Test_parseErlangString_escapedByte(t *testing.T) { // a backslash escape mid-string used to always error out, since the bounds // check at the escape site was inverted (len(data) >= *i is true for // almost every position, not just an out-of-range one). data := []byte(`"a\"b"`) i := 0 got, err := parseErlangString(data, &i) require.NoError(t, err) assert.Equal(t, `a"b`, got.String()) } func TestParseErlang_deepNesting(t *testing.T) { // 4MB of '[' overflowed the goroutine stack (fatal, not recoverable) before nesting was bounded _, err := parseErlang(strings.NewReader(strings.Repeat("[a,", 4<<20))) require.ErrorIs(t, err, errTooDeep) // the error echoes a clipped window of the single-line input, not the whole thing assert.Less(t, len(err.Error()), 1024) } func Test_parseErlang_truncated(t *testing.T) { // each of these read past the end of the input before end-of-input was checked tests := []struct { name string content string wantErr require.ErrorAssertionFunc }{ {name: "whitespace only", content: " ", wantErr: require.Error}, {name: "unterminated list", content: "[a", wantErr: require.Error}, {name: "unterminated empty list", content: "[ ", wantErr: require.Error}, {name: "lone binary string opener", content: "<", wantErr: require.Error}, {name: "comment ending in CR", content: "%\r", wantErr: require.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error require.NotPanics(t, func() { _, err = parseErlang(strings.NewReader(tt.content)) }) tt.wantErr(t, err) }) } }