package systemdconfig import ( "errors" "testing" "github.com/stretchr/testify/assert" ) var ( defaultSectionLines = "# This file is part of systemd.\n#\n# systemd is free software; you can redistribute it and/or modify it\n# under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation; either version 2.1 of the License, or\n# (at your option) any later version.\n#\n# Entries in this file show the compile time defaults.\n# You can change settings by editing this file.\n# Defaults can be restored by simply deleting this file.\n#\n# See systemd.syntax(7) for details." defaultSection = &Section{ map[string]*Field{ "!0": {"", "", "# This file is part of systemd."}, "!1": {"", "", "#"}, "!2": {"", "", "# systemd is free software; you can redistribute it and/or modify it"}, "!3": {"", "", "# under the terms of the GNU Lesser General Public License as published by"}, "!4": {"", "", "# the Free Software Foundation; either version 2.1 of the License, or"}, "!5": {"", "", "# (at your option) any later version."}, "!6": {"", "", "#"}, "!7": {"", "", "# Entries in this file show the compile time defaults."}, "!8": {"", "", "# You can change settings by editing this file."}, "!9": {"", "", "# Defaults can be restored by simply deleting this file."}, "!10": {"", "", "#"}, "!11": {"", "", "# See systemd.syntax(7) for details."}, }, []string{"!0", "!1", "!2", "!3", "!4", "!5", "!6", "!7", "!8", "!9", "!10", "!11"}, } testSectionAHeader = "[Section A]" testSectionALines = "KeyOne=value 1\nKeyTwo=value 2\nKeyThree=value 3\n#KeyFour=value 4 ; comment\n;KeyFive=value 5\n\nKeySix=value 6" testSectionA = &Section{ map[string]*Field{ "KeyOne": {"KeyOne", "value 1", ""}, "KeyTwo": {"KeyTwo", "value 2", ""}, "KeyThree": {"KeyThree", "value 3", ""}, "KeyFour": {"#KeyFour", "value 4", "; comment"}, "KeyFive": {";KeyFive", "value 5", ""}, "!5": {"", "", ""}, "KeySix": {"KeySix", "value 6", ""}, }, []string{"KeyOne", "KeyTwo", "KeyThree", "KeyFour", "KeyFive", "!5", "KeySix"}, } testSectionAUpdated = &Section{ map[string]*Field{ "KeyOne": {"KeyOne", "value 1", ""}, "KeyTwo": {"KeyTwo", "value updated", ""}, "KeyThree": {"KeyThree", "value 3", ""}, "KeyFour": {"#KeyFour", "value 4", "; comment"}, "KeyFive": {";KeyFive", "value 5", ""}, "!5": {"", "", ""}, "KeySix": {"KeySix", "value 6", ""}, }, []string{"KeyOne", "KeyTwo", "KeyThree", "KeyFour", "KeyFive", "!5", "KeySix"}, } testSectionAKeyRemoved = &Section{ map[string]*Field{ "KeyOne": {"KeyOne", "value 1", ""}, "KeyTwo": {"KeyTwo", "value 2", ""}, "KeyFour": {"#KeyFour", "value 4", "; comment"}, "KeyFive": {";KeyFive", "value 5", ""}, "!5": {"", "", ""}, "KeySix": {"KeySix", "value 6", ""}, }, []string{"KeyOne", "KeyTwo", "KeyFour", "KeyFive", "!5", "KeySix"}, } testSectionBHeader = "[Section B]" testSectionBLines = "#KeyOne=value 1\n\nKeyTwo=value 2 # comment\n;KeyThree=value 3\n#KeyFour=value 4" testSectionB = &Section{ map[string]*Field{ "KeyOne": {"#KeyOne", "value 1", ""}, "!1": {"", "", ""}, "KeyTwo": {"KeyTwo", "value 2", "# comment"}, "KeyThree": {";KeyThree", "value 3", ""}, "KeyFour": {"#KeyFour", "value 4", ""}, }, []string{"KeyOne", "!1", "KeyTwo", "KeyThree", "KeyFour"}, } testSectionBUpdated = &Section{ map[string]*Field{ "KeyOne": {"#KeyOne", "value 1", ""}, "!1": {"", "", ""}, "KeyTwo": {"KeyTwo", "value 2", "# comment"}, "KeyThree": {";KeyThree", "value 3", ""}, "KeyFour": {"KeyFour", "value updated", ""}, }, []string{"KeyOne", "!1", "KeyTwo", "KeyThree", "KeyFour"}, } testSectionCHeader = "[Section C]" testSectionCLines = "#KeyOne=value 1\n#KeyTwo=\n#KeyThree=value 3\n#KeyFour=value 4\n# comment\n;KeyFive=value 5" testSectionC = &Section{ map[string]*Field{ "KeyOne": {"#KeyOne", "value 1", ""}, "KeyTwo": {"#KeyTwo", "", ""}, "KeyThree": {"#KeyThree", "value 3", ""}, "KeyFour": {"#KeyFour", "value 4", ""}, "!4": {"", "", "# comment"}, "KeyFive": {";KeyFive", "value 5", ""}, }, []string{"KeyOne", "KeyTwo", "KeyThree", "KeyFour", "!4", "KeyFive"}, } testSectionCKeyAdded = &Section{ map[string]*Field{ "KeyOne": {"#KeyOne", "value 1", ""}, "KeyTwo": {"#KeyTwo", "", ""}, "KeyThree": {"#KeyThree", "value 3", ""}, "KeyFour": {"#KeyFour", "value 4", ""}, "!4": {"", "", "# comment"}, "KeyFive": {";KeyFive", "value 5", ""}, "NewKey": {"NewKey", "value added", ""}, }, []string{"KeyOne", "KeyTwo", "KeyThree", "KeyFour", "!4", "KeyFive", "NewKey"}, } testSectionD = &Section{map[string]*Field{}, []string{}} testSystemdConfig = &SystemdConfig{ sections: map[string]*Section{defaultSectionHeader: defaultSection, testSectionAHeader: testSectionA, testSectionBHeader: testSectionB, testSectionCHeader: testSectionC}, orderedSectionHeaders: []string{defaultSectionHeader, testSectionAHeader, testSectionBHeader, testSectionCHeader}, } testSystemdConfigKeyValueUpdated = &SystemdConfig{ sections: map[string]*Section{defaultSectionHeader: defaultSection, testSectionAHeader: testSectionAUpdated, testSectionBHeader: testSectionB, testSectionCHeader: testSectionC}, orderedSectionHeaders: []string{defaultSectionHeader, testSectionAHeader, testSectionBHeader, testSectionCHeader}, } testSystemdConfigCommentedKeyValueUpdated = &SystemdConfig{ sections: map[string]*Section{defaultSectionHeader: defaultSection, testSectionAHeader: testSectionA, testSectionBHeader: testSectionBUpdated, testSectionCHeader: testSectionC}, orderedSectionHeaders: []string{defaultSectionHeader, testSectionAHeader, testSectionBHeader, testSectionCHeader}, } testSystemdConfigKeyAdded = &SystemdConfig{ sections: map[string]*Section{defaultSectionHeader: defaultSection, testSectionAHeader: testSectionA, testSectionBHeader: testSectionB, testSectionCHeader: testSectionCKeyAdded}, orderedSectionHeaders: []string{defaultSectionHeader, testSectionAHeader, testSectionBHeader, testSectionCHeader}, } testSystemdConfigKeyRemoved = &SystemdConfig{ sections: map[string]*Section{defaultSectionHeader: defaultSection, testSectionAHeader: testSectionAKeyRemoved, testSectionBHeader: testSectionB, testSectionCHeader: testSectionC}, orderedSectionHeaders: []string{defaultSectionHeader, testSectionAHeader, testSectionBHeader, testSectionCHeader}, } testSystemdConfigSectionRemoved = &SystemdConfig{ sections: map[string]*Section{defaultSectionHeader: defaultSection, testSectionAHeader: testSectionA, testSectionCHeader: testSectionC}, orderedSectionHeaders: []string{defaultSectionHeader, testSectionAHeader, testSectionCHeader}, } testData = []byte(defaultSectionLines + "\n\n" + testSectionAHeader + "\n" + testSectionALines + "\n\n" + testSectionBHeader + "\n" + testSectionBLines + "\n\n" + testSectionCHeader + "\n" + testSectionCLines) testInvalidKey = []byte(defaultSectionLines + "\n\n" + testSectionAHeader + "\n" + "KeyOne=value 1\nKey!!!=value 2\nKeyThree=value 3") testEmptyKey = []byte(defaultSectionLines + "\n\n" + testSectionAHeader + "\n" + "KeyOne=value 1\n#=value 2\nKeyThree=value 3") testValueWithoutKey = []byte(defaultSectionLines + "\n\n" + testSectionAHeader + "\n" + "KeyOne=value 1\n=value 2\nKeyThree=value 3") testMultiLineValueNotSupported = []byte(defaultSectionLines + "\n\n" + testSectionAHeader + "\n" + "KeyOne=value 1\nKeyTwo=value 2\nKeyThree=value 3\\\ncontinued") testInvalidLine = []byte(defaultSectionLines + "\n\n" + testSectionAHeader + "\n" + "KeyOne=value 1\ninvalid line\nKeyThree=value 3") ) // Tests SyntaxError displays correct message func TestSyntaxError(t *testing.T) { expectedErr := errors.New("context: error message") err := &SyntaxError{"context", errors.New("error message")} assert.Equal(t, expectedErr.Error(), err.Error()) } // Tests UnsupportedError displays correct message func TestUnsupportedError(t *testing.T) { expectedErr := errors.New("context: error message") err := &UnsupportedError{"context", errors.New("error message")} assert.Equal(t, expectedErr.Error(), err.Error()) } // Tests SystemdConfig can be built from byte data as expected func TestLoadFromBytes(t *testing.T) { tests := []struct { inputBytes []byte expectedSystemConfig *SystemdConfig expectedErr error }{ {testData, testSystemdConfig, nil}, // create SystemdConfig as expected {testInvalidKey, nil, &SyntaxError{}}, // invalid characters in key {testEmptyKey, nil, &SyntaxError{}}, // empty commented key (#=...) should fail {testValueWithoutKey, nil, &SyntaxError{}}, // value without key (=...) should fail {testInvalidLine, nil, &SyntaxError{}}, // non-commented text (abc...) should fail {testMultiLineValueNotSupported, nil, &SyntaxError{}}, // multi-line values are not supported and should fail } for i, test := range tests { systemdConfig, err := LoadFromBytes(test.inputBytes) assert.Equal(t, test.expectedSystemConfig, systemdConfig) if test.expectedErr != nil { assert.ErrorAs(t, err, &tests[i].expectedErr) } else { assert.NoError(t, err) } } } // Tests only syntactically correct section headers are validated func TestSectionHeaderIsValid(t *testing.T) { tests := []struct { inputLine string expectedResult bool }{ {"[Time]", true}, {"[section]", true}, {"not-section", false}, {"not[section]", false}, {"[[not] section]", false}, {"[test section]", true}, } for _, test := range tests { result := sectionHeaderIsValid(test.inputLine) assert.Equal(t, test.expectedResult, result) } } // Tests byte output of the SystemdConfig is as expected func TestBytes(t *testing.T) { bytes := testSystemdConfig.Bytes() assert.Equal(t, testData, bytes) } // Test changes to deep copy do not affect original func TestDeepCopySystemdConfig(t *testing.T) { systemdConfig := testSystemdConfig // original systemdConfigCopied := systemdConfig.DeepCopy() // (deep) copy _ = systemdConfigCopied.Section("[Section A]").SetField("KeyOne", "value updated") // update copy assert.NotEqual(t, systemdConfig, systemdConfigCopied) // assert copy was changed assert.Equal(t, testSystemdConfig, systemdConfig) // assert original has not been changed } // Test correct sections are returned for SystemdConfig func TestListSectionsFromSystemdConfig(t *testing.T) { expectedSections := []string{testSectionAHeader, testSectionBHeader, testSectionCHeader} sections := testSystemdConfig.ListSections() assert.Equal(t, expectedSections, sections) } // Test section can be added to SystemdConfig func TestAddSectionToSystemdConfig(t *testing.T) { // section is removed as expected systemdConfig := testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.AddSection("[Section D]")) assert.Equal(t, testSectionD, systemdConfig.Section("[Section D]")) // invalid section header returns syntax error systemdConfig = testSystemdConfig.DeepCopy() expectedErr := &SyntaxError{} err := systemdConfig.AddSection("Section D") assert.ErrorAs(t, err, &expectedErr) } // Tests a section can be removed from the SystemdConfig as expected func TestRemoveSectionFromSystemdConfig(t *testing.T) { // section is removed as expected systemdConfig := testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.RemoveSection("[Section B]")) assert.Equal(t, testSystemdConfigSectionRemoved, systemdConfig) // invalid section header returns syntax error systemdConfig = testSystemdConfig.DeepCopy() expectedErr := &SyntaxError{} err := systemdConfig.RemoveSection("Section A") assert.ErrorAs(t, err, &expectedErr) // non-existent section header returns nil and removes nothing systemdConfig = testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.RemoveSection("[Section D]")) assert.Equal(t, testSystemdConfig, systemdConfig) } // Test check for section in SystemdConfig returns result as expected func TestSystemdConfigHasSection(t *testing.T) { assert.True(t, testSystemdConfig.HasSection("[Section A]")) assert.False(t, testSystemdConfig.HasSection("[Section D]")) assert.False(t, testSystemdConfig.HasSection("Section A")) assert.False(t, testSystemdConfig.HasSection("")) } // Test keys in section are returned as expected func TestListFieldsForSection(t *testing.T) { expectedKeys := []string{"KeyOne", "KeyTwo", "KeyThree", "KeyFour", "KeyFive", "KeySix"} keys := testSystemdConfig.Section("[Section A]").ListFields() assert.Equal(t, expectedKeys, keys) } // Tests key:value can be set for section in the SystemdConfig as expected func TestSetFieldInSection(t *testing.T) { // update key value systemdConfig := testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.Section("[Section A]").SetField("KeyTwo", "value updated")) assert.Equal(t, testSystemdConfigKeyValueUpdated, systemdConfig) // uncomment and update key value systemdConfig = testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.Section("[Section B]").SetField("KeyFour", "value updated")) assert.Equal(t, testSystemdConfigCommentedKeyValueUpdated, systemdConfig) // add new key value to section systemdConfig = testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.Section("[Section C]").SetField("NewKey", "value added")) assert.Equal(t, testSystemdConfigKeyAdded, systemdConfig) // invalid key returns error and doesn't set new key systemdConfig = testSystemdConfig.DeepCopy() expectedErr := &SyntaxError{} err := systemdConfig.Section("[Section C]").SetField("NewKey!", "value added") assert.ErrorAs(t, err, &expectedErr) assert.False(t, systemdConfig.Section("[Section C]").HasField("NewKey!")) // invalid value returns error and doesn't set new key systemdConfig = testSystemdConfig.DeepCopy() expectedErr = &SyntaxError{} err = systemdConfig.Section("[Section C]").SetField("NewKey", "value #added") assert.ErrorAs(t, err, &expectedErr) assert.False(t, systemdConfig.Section("[Section C]").HasField("NewKey")) // multi-line value returns error and doesn't set new key systemdConfig = testSystemdConfig.DeepCopy() expectedUnsupporedErr := &UnsupportedError{} err = systemdConfig.Section("[Section C]").SetField("NewKey", "value added \\") assert.ErrorAs(t, err, &expectedUnsupporedErr) assert.False(t, systemdConfig.Section("[Section C]").HasField("NewKey")) } // Tests key can be removed from section in the SystemdConfig as expected func TestRemoveKeyFromSection(t *testing.T) { // remove key from section systemdConfig := testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.Section("[Section A]").RemoveField("KeyThree")) assert.Equal(t, testSystemdConfigKeyRemoved.Section("[Section A]"), systemdConfig.Section("[Section A]")) // // non-existent key in section returns nil and removes nothing systemdConfig = testSystemdConfig.DeepCopy() assert.NoError(t, systemdConfig.Section("[Section A]").RemoveField("KeyTen")) assert.Equal(t, testSystemdConfig, systemdConfig) // invalid key returns error systemdConfig = testSystemdConfig.DeepCopy() expectedErr := &SyntaxError{} err := systemdConfig.Section("[Section C]").RemoveField("NewKey!") assert.ErrorAs(t, err, &expectedErr) } // Test check for key in section returns result as expected func TestSectionHasKey(t *testing.T) { assert.True(t, testSystemdConfig.Section("[Section A]").HasField("KeyOne")) // existent key assert.False(t, testSystemdConfig.Section("[Section A]").HasField("KeyTen")) // non-existent key assert.False(t, testSystemdConfig.Section("[Section A]").HasField("")) // empty key does not exist } // Test check for key literal returns result as expected func TestFieldHasKey(t *testing.T) { assert.True(t, testSystemdConfig.Section("[Section A]").Field("KeyOne").HasKey()) // uncommented key has literal assert.True(t, testSystemdConfig.Section("[Section A]").Field("KeyFive").HasKey()) // commented key has literal assert.False(t, testSystemdConfig.Section("[Section C]").Field("!4").HasKey()) // comment line has no literal assert.False(t, testSystemdConfig.Section("[Section A]").Field("!5").HasKey()) // empty line has no literal } // Test key literal can be commented func TestFieldKeyIsCommented(t *testing.T) { // uncommented key is commented expectedKey := "#KeyOne" systemdConfig := testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyOne").CommentKey() assert.Equal(t, expectedKey, systemdConfig.Section("[Section A]").Field("KeyOne").GetKey()) // commented key is left as is expectedKey = ";KeyFive" systemdConfig = testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyFive").CommentKey() assert.Equal(t, expectedKey, systemdConfig.Section("[Section A]").Field("KeyFive").GetKey()) } // Test key literal can be uncommented func TestFieldKeyIsUncommented(t *testing.T) { // commented key is uncommented expectedKey := "KeyFive" systemdConfig := testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyFive").UncommentKey() assert.Equal(t, expectedKey, systemdConfig.Section("[Section A]").Field("KeyFive").GetKey()) // uncommented key is left as is expectedKey = "KeyOne" systemdConfig = testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyOne").UncommentKey() assert.Equal(t, expectedKey, systemdConfig.Section("[Section A]").Field("KeyOne").GetKey()) } // Test value can be set for field func TestSetValueInField(t *testing.T) { // value is set as expected expectedValue := "new value" systemdConfig := testSystemdConfig.DeepCopy() err := systemdConfig.Section("[Section A]").Field("KeyOne").SetValue(expectedValue) assert.NoError(t, err) assert.Equal(t, expectedValue, systemdConfig.Section("[Section A]").Field("KeyOne").GetValue()) // empty value is set as expected expectedValue = "" systemdConfig = testSystemdConfig.DeepCopy() err = systemdConfig.Section("[Section A]").Field("KeyOne").SetValue(expectedValue) assert.NoError(t, err) assert.Equal(t, expectedValue, systemdConfig.Section("[Section A]").Field("KeyOne").GetValue()) // invalid value returns error isn't set invalidValue := "new #value" expectedErr := &SyntaxError{} systemdConfig = testSystemdConfig.DeepCopy() err = systemdConfig.Section("[Section A]").Field("KeyOne").SetValue(invalidValue) assert.ErrorAs(t, err, &expectedErr) assert.Equal(t, testSystemdConfig.Section("[Section A]").Field("KeyOne").GetValue(), systemdConfig.Section("[Section A]").Field("KeyOne").GetValue()) // multi-line value returns error isn't set multiLineValue := "new value \\" expectedUnsupporedErr := &UnsupportedError{} systemdConfig = testSystemdConfig.DeepCopy() err = systemdConfig.Section("[Section A]").Field("KeyOne").SetValue(multiLineValue) assert.ErrorAs(t, err, &expectedUnsupporedErr) assert.Equal(t, testSystemdConfig.Section("[Section A]").Field("KeyOne").GetValue(), systemdConfig.Section("[Section A]").Field("KeyOne").GetValue()) } // Test value can be set for field func TestRemoveValueFromField(t *testing.T) { // value is removed systemdConfig := testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyOne").RemoveValue() assert.Equal(t, "", systemdConfig.Section("[Section A]").Field("KeyOne").GetValue()) // no-value is unchanged systemdConfig = testSystemdConfig.DeepCopy() systemdConfig.Section("[Section C]").Field("KeyTwo").RemoveValue() assert.Equal(t, testSystemdConfig.Section("[Section C]").Field("KeyTwo").GetValue(), systemdConfig.Section("[Section C]").Field("KeyTwo").GetValue()) } // Test check for value in field returns result as expected func TestFieldHasValue(t *testing.T) { assert.True(t, testSystemdConfig.Section("[Section A]").Field("KeyOne").HasValue()) assert.False(t, testSystemdConfig.Section("[Section C]").Field("KeyTwo").HasValue()) } // Test comment can be set for field func TestSetCommentInField(t *testing.T) { expectedComment := "# new comment" systemdConfig := testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyOne").SetComment("new comment") assert.Equal(t, expectedComment, systemdConfig.Section("[Section A]").Field("KeyOne").GetComment()) } // Test comment can be set for field func TestRemoveCommentFromField(t *testing.T) { // in-line comment is removed systemdConfig := testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyFour").RemoveComment() assert.Equal(t, "", systemdConfig.Section("[Section A]").Field("KeyFour").GetComment()) // comment line becomes empty systemdConfig = testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("!5").RemoveComment() assert.True(t, systemdConfig.Section("[Section A]").Field("!5").IsEmptyLine()) // no-comment is unchanged systemdConfig = testSystemdConfig.DeepCopy() systemdConfig.Section("[Section A]").Field("KeyOne").RemoveComment() assert.Equal(t, testSystemdConfig.Section("[Section A]").Field("KeyOne").GetComment(), systemdConfig.Section("[Section A]").Field("KeyOne").GetComment()) } // Test check for comment in field returns result as expected func TestFieldHasComment(t *testing.T) { assert.False(t, testSystemdConfig.Section("[Section A]").Field("KeyOne").HasComment()) // no comment key:value assert.False(t, testSystemdConfig.Section("[Section A]").Field("!5").HasComment()) // no comment empty line assert.True(t, testSystemdConfig.Section("[Section A]").Field("KeyFour").HasComment()) // in-line comment assert.True(t, testSystemdConfig.Section("[Section C]").Field("!4").HasComment()) // comment line }