Code Monkey home page Code Monkey logo

envi's People

Contributors

kochava-ci avatar nilium avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

envi's Issues

Depth counters should be reset when adjacent struct fields are filled

Depth counters should be reset if a struct field is found to be set, even if there are no non-struct fields assigned in that struct.

This may require re-parsing previously skipped fields with the reset depth value.

Example test to demonstrate a case where a struct field is set but the counter isn't re-set for other struct fields:

func TestCyclicalTypesAdjacent(t *testing.T) {
	type P struct {
		V    int
		L, R *P
	}

	runner := func(max int, want *P, env Env) func(*testing.T) {
		return func(t *testing.T) {
			dest := new(P)
			r := Reader{Source: env, Sep: "_", MaxDepth: max}
			err := r.Getenv(dest, "Cycle")
			if err != nil && !IsNoValue(err) {
				t.Fatalf("unexpected error: %v", err)
			}
			if !reflect.DeepEqual(dest, want) {
				// Encode as JSON just to make it easier to visualize
				jsondest, _ := json.Marshal(dest)
				jsonwant, _ := json.Marshal(want)
				t.Fatalf("got\t%s\nwant\t%s",
					jsondest,
					jsonwant,
				)
			}
		}
	}

	t.Run("ResetCounterOnStructMatch", runner(
		2, // max depth
		&P{
			R: &P{V: 2},
			L: &P{L: &P{V: 4}},
		},
		Values{ //     1 2 3 4 5 6 7 7
			"Cycle_R_V":   {"2"},
			"Cycle_L_L_V": {"4"},
		},
	))

	t.Run("NoMatchOutsideDepth", runner(
		2, // max depth
		&P{},
		Values{ //     1 2 3 4 5 6 7 7
			"Cycle_L_L_V": {"4"},
		},
	))
}

Cyclical types cannot be unmarshalled (results in ~infinite recursion)

Cyclical types, namely those that refer to themselves where the unmarshaller can see it, such as the Cycle type in the example below, cannot be unmarshalled. They result in recursion that will eventually kill a Go program. It doesn't matter if the cycle is direct or indirect (i.e., a sequence of types that eventually forms a cycle), it's just guaranteed to wreck things.

func TestCyclicalTypes(t *testing.T) {
	type Cycle struct {
		Value int
		Next  *Cycle
	}

	dest := new(Cycle)
	env := Values{"Cycle_Value": {"1"}}

	r := Reader{Source: env}
	err := r.Getenv(dest, "Cycle")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

As in the example above, this doesn't require that there is even a way to unmarshal the Next value -- since envi begins unmarshalling before it checks for keys' existence, it doesn't know whether Next can have a value until it iterates over its fields. By the time it hits Next.Next it has to then ask whether that has any fields assigned, since the absence of fields for its predecessor has no bearing on whether it would have fields.

It feels like depth needs to be capped for unchecked recursion and possibly an optional interface added to make it easier to determine if there are keys ahead of time. For OS environments and envi.Values, this is fairly easy -- check prefixes on keys. For other Env implementations where the keyset is not known (for whatever reason), tracking depth on unmarshalling seems like the way to go.


Test output:

$ go test -run TestCyclical
signal: killed
FAIL    github.com/Kochava/envi 102.353s

Fields with pointer types cannot be unmarshalled

At some point this broke, so it's no longer possible to unmarshal a type with a pointer field. For example:

func TestPointerFields(t *testing.T) {
	runner := func(dest interface{}) func(*testing.T) {
		return func(t *testing.T) {
			env := Values{"Data_Value_Size": {"16"}}

			r := Reader{Source: env, Sep: "_"}
			err := r.Getenv(dest, "Data")
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
		}
	}

	t.Run("StructValue", runner(new(
		struct {
			Value struct {
				Size int
			}
		},
	)))

	t.Run("StructPointer", runner(new(
		struct {
			Value *struct {
				Size int
			}
		},
	)))
}

The first subtest, StructValue, succeeds. The second, StructPointer, fails. This is supported in the internal version of envi, so I introduced a new bug between v0 and v1 during the refactoring. Amazingly, none of the existing tests actually covers this.


Stack trace:

panic: reflect.Set: value of type *struct { Size int } is not assignable to type struct { Size int } [recovered]
        panic: reflect.Set: value of type *struct { Size int } is not assignable to type struct { Size int }

goroutine 237 [running]:
testing.tRunner.func1(0xc42011db30)
        /usr/local/go/src/testing/testing.go:742 +0x29d
panic(0x1129fa0, 0xc42011e710)
        /usr/local/go/src/runtime/panic.go:505 +0x229
reflect.Value.assignTo(0x1125760, 0xc42000c0d8, 0x196, 0x115ed15, 0xb, 0x11385e0, 0x0, 0x1125701, 0x1125760, 0xc420111b30)
        /usr/local/go/src/reflect/value.go:2235 +0x427
reflect.Value.Set(0x11385e0, 0xc420111b30, 0x199, 0x1125760, 0xc42000c0d8, 0x196)
        /usr/local/go/src/reflect/value.go:1373 +0xa4
github.com/Kochava/envi.(*Reader).loadStructField(0xc420081f60, 0x1138660, 0xc42000c0d8, 0x199, 0x117c7c0, 0x1138660, 0x0, 0x115d9b0, 0x4, 0x1138660, ...)
        …/github.com/Kochava/envi/reader.go:438 +0x3ec
github.com/Kochava/envi.(*Reader).loadStruct(0xc420081f60, 0x1138660, 0xc42000c0d8, 0x199, 0x115d9b0, 0x4, 0x20, 0xf4356b620113dc60)
        …/github.com/Kochava/envi/reader.go:398 +0x12c
github.com/Kochava/envi.(*Reader).loadReflect(0xc420045f60, 0x1125820, 0xc42000c0d8, 0x0, 0x0, 0x115d9b0, 0x4, 0x0, 0x0)
        …/github.com/Kochava/envi/reader.go:217 +0x23a
github.com/Kochava/envi.(*Reader).load(0xc420045f60, 0x1125820, 0xc42000c0d8, 0x0, 0x0, 0x115d9b0, 0x4, 0x2b61c81951471eac, 0x100fdb8)
        …/github.com/Kochava/envi/reader.go:120 +0x1e9
github.com/Kochava/envi.(*Reader).Getenv(0xc420045f60, 0x1125820, 0xc42000c0d8, 0x115d9b0, 0x4, 0x117a840, 0xc420113740)
        …/github.com/Kochava/envi/reader.go:60 +0x2a3
github.com/Kochava/envi.TestPointerFields.func1.1(0xc42011db30)
        …/github.com/Kochava/envi/structptr_test.go:11 +0x16f
testing.tRunner(0xc42011db30, 0xc420113700)
        /usr/local/go/src/testing/testing.go:777 +0xd0
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:824 +0x2e0
exit status 2
FAIL    github.com/Kochava/envi 0.013s

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.