Skip to content

Commit

Permalink
Add tests to autoini package and update its logic (#3)
Browse files Browse the repository at this point in the history
* Start implementing autoini tests, return errors instead of failing

* Make ini parser setters use a generic method, check for exported Configurable type fields

* Simplified setGenericKey interface, updated tests
  • Loading branch information
igor-sikachyna authored Nov 25, 2024
1 parent b57273f commit 5032a4c
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 53 deletions.
142 changes: 98 additions & 44 deletions autoini/autoini.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package autoini

import (
"gopkg.in/ini.v1"
"log"
"errors"
"reflect"
"unicode"

"gopkg.in/ini.v1"
)

type Configurable interface {
type Configurable interface{}

type ImplementsOptional interface {
Optional(key string) bool
}

Expand All @@ -24,86 +28,136 @@ type ImplementsDefaultBool interface {

type ImplementsPostInit interface {
// Must be implemented as a pointer receiver
PostInit()
PostInit() (err error)
}

func getKey(key string, optional bool, cfg *ini.File) (valueInConfig *ini.Key) {
type implementationChecker interface{}

func isOptional[T Configurable](result T, key string) bool {
if def, ok := any(result).(ImplementsOptional); ok {
return def.Optional(key)
}
return false
}

func getKey(key string, optional bool, cfg *ini.File) (valueInConfig *ini.Key, err error) {
valueExists := cfg.Section("").HasKey(key)
if !valueExists {
if !optional {
log.Fatalln("Non-optional config file key not found: ", key)
return valueInConfig, errors.New("non-optional config file key not found: " + key)
}
} else {
valueInConfig = cfg.Section("").Key(key)
}
return valueInConfig
return
}

func setStringKey[T Configurable](result T, key string, value reflect.Value, cfg *ini.File) {
valueInConfig := getKey(key, result.Optional(key), cfg)
if valueInConfig != nil {
value.SetString(valueInConfig.String())
} else if def, ok := any(result).(ImplementsDefaultString); ok {
value.SetString(def.DefaultString(key))
type Setter func(value reflect.Value, valueInConfig *ini.Key) (err error)
type DefaultHandler[Implementation implementationChecker] func(def Implementation, key string, value reflect.Value) (err error)

func setGenericKey[T Configurable, Implementation implementationChecker](result T, key string, value reflect.Value, cfg *ini.File, setter Setter, defaultHandler DefaultHandler[Implementation]) (err error) {
valueInConfig, err := getKey(key, isOptional(result, key), cfg)
if err != nil {
return err
}
}

func setIntKey[T Configurable](result T, key string, value reflect.Value, cfg *ini.File) {
valueInConfig := getKey(key, result.Optional(key), cfg)
if valueInConfig != nil {
val, err := valueInConfig.Int64()
if err != nil {
log.Fatalf("Config file key %v is not an int", key)
}
value.SetInt(val)
} else if def, ok := any(result).(ImplementsDefaultInt); ok {
value.SetInt(int64(def.DefaultInt(key)))
err = setter(value, valueInConfig)
} else if def, ok := any(result).(Implementation); ok {
err = defaultHandler(def, key, value)
} else {
return errors.New("did not find a value or default value for key: " + key)
}
return
}

func setBoolKey[T Configurable](result T, key string, value reflect.Value, cfg *ini.File) {
valueInConfig := getKey(key, result.Optional(key), cfg)
if valueInConfig != nil {
val, err := valueInConfig.Bool()
if err != nil {
log.Fatalf("Config file key %v is not a bool", key)
}
value.SetBool(val)
} else if def, ok := any(result).(ImplementsDefaultBool); ok {
value.SetBool(def.DefaultBool(key))
}
func setStringKey[T Configurable](result T, key string, value reflect.Value, cfg *ini.File) (err error) {
return setGenericKey(result, key, value, cfg,
func(value reflect.Value, valueInConfig *ini.Key) (err error) {
value.SetString(valueInConfig.String())
return
},
func(def ImplementsDefaultString, key string, value reflect.Value) (err error) {
value.SetString(def.DefaultString(key))
return
},
)
}

func ReadIni[T Configurable](path string) (result T) {
func setIntKey[T Configurable](result T, key string, value reflect.Value, cfg *ini.File) (err error) {
return setGenericKey(result, key, value, cfg,
func(value reflect.Value, valueInConfig *ini.Key) (err error) {
val, err := valueInConfig.Int64()
if err != nil {
return errors.New("config file key " + key + " is not an int")
}
value.SetInt(val)
return
},
func(def ImplementsDefaultInt, key string, value reflect.Value) (err error) {
value.SetInt(int64(def.DefaultInt(key)))
return
},
)
}

func setBoolKey[T Configurable](result T, key string, value reflect.Value, cfg *ini.File) (err error) {
return setGenericKey(result, key, value, cfg,
func(value reflect.Value, valueInConfig *ini.Key) (err error) {
val, err := valueInConfig.Bool()
if err != nil {
return errors.New("config file key " + key + " is not a bool")
}
value.SetBool(val)
return
},
func(def ImplementsDefaultBool, key string, value reflect.Value) (err error) {
value.SetBool(def.DefaultBool(key))
return
},
)
}

func ReadIni[T Configurable](path string) (result T, err error) {
cfg, err := ini.Load(path)
// TODO: return an error instead of exiting
if err != nil {
log.Fatalf("Fail to read file: %v", err)
return result, err
}

configReflection := reflect.ValueOf(&result)
typeOfConfig := reflect.Indirect(configReflection).Type()

for i := 0; i < reflect.Indirect(configReflection).NumField(); i++ {
var configValueType = reflect.ValueOf(reflect.Indirect(configReflection).Field(i).Interface()).Type()
var fieldName = typeOfConfig.Field(i).Name
if len(fieldName) == 0 || !unicode.IsUpper(rune(fieldName[0])) {
return result, errors.New("field does not exist or is not exported: " + fieldName)
}
var configValueType = reflect.ValueOf(reflect.Indirect(configReflection).Field(i).Interface()).Type()

switch configValueType.Name() {
case "string":
setStringKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
err = setStringKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
case "int":
setIntKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
err = setIntKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
case "int64":
setIntKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
err = setIntKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
case "bool":
setBoolKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
err = setBoolKey(result, fieldName, reflect.Indirect(configReflection).Field(i), cfg)
default:
log.Fatalf("Unsupported ini value type: %v", configValueType.Name())
return result, errors.New("unsupported ini value type: " + configValueType.Name())
}

if err != nil {
return
}
}

// Convert to a pointer to support pointer receivers
if def, ok := any(&result).(ImplementsPostInit); ok {
def.PostInit()
err = def.PostInit()
if err != nil {
return
}
}

return
Expand Down
100 changes: 100 additions & 0 deletions autoini/autoini_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package autoini

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
)

type SimpleValueNotExported struct {
value bool
}

type SimpleValue struct {
Value bool
}

type Values struct {
Value1 bool
Value2 int
Value3 string
}

func (v Values) Optional(key string) bool {
return false
}

type ValuesDefault struct {
Value1 bool
Value2 int
Value3 string
}

func (v ValuesDefault) Optional(key string) bool {
return true
}

func (v ValuesDefault) DefaultString(key string) string {
return "string value"
}

func (v ValuesDefault) DefaultInt(key string) int {
return 123
}

func (v ValuesDefault) DefaultBool(key string) bool {
return false
}

func (v ValuesDefault) PostInit() (err error) {
if v.Value2 == 42 {
return errors.New("42 is not allowed")
}

return nil
}

func TestReadIniInvalid(t *testing.T) {
var assert = assert.New(t)

var ini1, err = ReadIni[SimpleValueNotExported]("./test/a.ini")
assert.NotEqual(nil, err, "Was able to read an invalid ini file")
assert.Contains(err.Error(), "key-value delimiter not found")
assert.Equal(false, ini1.value, "Incorrect value returned")

ini1, err = ReadIni[SimpleValueNotExported]("./test/b.ini")
assert.NotEqual(nil, err, "Was able to read an invalid ini file")
assert.Contains(err.Error(), "field does not exist or is not exported: value")
assert.Equal(false, ini1.value, "Incorrect value returned")

ini2, err := ReadIni[SimpleValue]("./test/b.ini")
assert.NotEqual(nil, err, "Was able to read an ini file without the required value")
assert.Contains(err.Error(), "non-optional config file key not found: Value")
assert.Equal(false, ini2.Value, "Incorrect value returned")
}

func TestReadIniValid(t *testing.T) {
var assert = assert.New(t)

var ini, err = ReadIni[Values]("./test/c.ini")
assert.Equal(nil, err, "Was not able to read a valid ini file")
assert.Equal(true, ini.Value1, "Incorrect Value1 returned")
assert.Equal(42, ini.Value2, "Incorrect Value2 returned")
assert.Equal("hello world", ini.Value3, "Incorrect Value3 returned")
}

func TestReadIniOptionalImplementationsValid(t *testing.T) {
var assert = assert.New(t)

var ini, err = ReadIni[ValuesDefault]("./test/empty.ini")
assert.Equal(nil, err, "Was not able to read a valid ini file")
assert.Equal(false, ini.Value1, "Incorrect Value1 returned")
assert.Equal(123, ini.Value2, "Incorrect Value2 returned")
assert.Equal("string value", ini.Value3, "Incorrect Value3 returned")

ini, err = ReadIni[ValuesDefault]("./test/c.ini")
assert.NotEqual(nil, err, "PostInit did not return an error")
assert.Contains(err.Error(), "42 is not allowed")
assert.Equal(42, ini.Value2, "Incorrect new Value2 returned")
}
1 change: 1 addition & 0 deletions autoini/test/a.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid abcd
1 change: 1 addition & 0 deletions autoini/test/b.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
value_other=true
3 changes: 3 additions & 0 deletions autoini/test/c.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Value1=true
Value2=42
Value3="hello world"
Empty file added autoini/test/empty.ini
Empty file.
6 changes: 4 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
)

func main() {
var config = autoini.ReadIni[Config]("config.ini")
fmt.Println(config)
var config, err = autoini.ReadIni[Config]("config.ini")
if err != nil {
log.Fatal(err)
}

mongo, err := mongodb.NewMongoDB(config.MongodbConnectionUrl, config.DatabaseName)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion mongodb/mongodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ func (m *MongoDB) GetLastDocumentFiltered(collection string, sortedKey string, f
}
opts := options.FindOne().SetSort(bson.D{{Key: sortedKey, Value: 1}}).SetSkip(count - 1)

// TODO: Return un-decoded result
result = mongoCollection.FindOne(ctx, filter, opts)
if result.Err() != nil {
return result, result.Err()
Expand Down
11 changes: 7 additions & 4 deletions query.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import "log"
import (
"errors"
)

type QueryConfig struct {
Name string // Internal only
Expand Down Expand Up @@ -71,11 +73,12 @@ func (q QueryConfig) DefaultBool(key string) bool {
return false
}

func (q *QueryConfig) PostInit() {
func (q *QueryConfig) PostInit() (err error) {
if q.ResultType != "string" && q.ResultType != "number" {
log.Fatalf("Invalid result type %v. Only \"string\" and \"number\" result types are supported", q.ResultType)
return errors.New("Invalid result type " + q.ResultType + ". Only \"string\" and \"number\" result types are supported")
}
if q.RequestBackend != "chrome" && q.RequestBackend != "go" {
log.Fatalf("Invalid request backend %v. Only \"chrome\" and \"go\" request backends are supported", q.RequestBackend)
return errors.New("Invalid request backend " + q.RequestBackend + ". Only \"chrome\" and \"go\" request backends are supported")
}
return
}
7 changes: 5 additions & 2 deletions tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,12 @@ func StartTrackers(queries []string, globalConfig Config, mongo mongodb.MongoDB,
for _, configPath := range queries {
var threadStopResponse = make(chan any)
stopChannels = append(stopChannels, threadStopResponse)
var config = autoini.ReadIni[QueryConfig](configPath)
var config, err = autoini.ReadIni[QueryConfig](configPath)
if err != nil {
log.Fatal(err)
}
config.Name = GetFileNameWithoutExtension(configPath)
var err = mongo.CreateCollection(config.Name)
err = mongo.CreateCollection(config.Name)
if err != nil {
log.Fatal(err)
}
Expand Down

0 comments on commit 5032a4c

Please sign in to comment.