engine: graph, util: Restore send/recv behaviour
A regression in 4b0cdf9123 caused the
basic send/recv functionality to break for simple scenarios. This was
due to inadequate testing, and a partial misunderstanding of the
situation.
New testing should hopefully catch more cases, but send/recv and
compile-time checks are still not as complete as is probably possible.
This commit is contained in:
@@ -165,12 +165,35 @@ func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[str
|
|||||||
//orig := value1
|
//orig := value1
|
||||||
dest := value2 // save the o.g. because we need the real dest!
|
dest := value2 // save the o.g. because we need the real dest!
|
||||||
|
|
||||||
// For situations where we send a variant to the resource!
|
// NOTE: Reminder: obj1 comes from st and it is the *<Res>Sends
|
||||||
for kind1 == reflect.Interface || kind1 == reflect.Ptr {
|
// struct which contains whichever fields that resource sends.
|
||||||
|
// For example, this might be *TestSends for the Test resource.
|
||||||
|
// The receiver is obj2 and that is actually the resource struct
|
||||||
|
// which is a *<Res> and which gets it's fields directly set on.
|
||||||
|
// For example, this might be *TestRes for the Test resource.
|
||||||
|
//fmt.Printf("obj1(%T): %+v\n", obj1, obj1)
|
||||||
|
//fmt.Printf("obj2(%T): %+v\n", obj2, obj2)
|
||||||
|
// Lastly, remember that many of the type incompatibilities are
|
||||||
|
// caught during type unification, and so we might have overly
|
||||||
|
// relaxed the checks here and something could slip by. If we
|
||||||
|
// find something, this code will need new checks added back.
|
||||||
|
|
||||||
|
// Here we unpack one-level, and then leave the complex stuff
|
||||||
|
// for the Into() method below.
|
||||||
|
// for kind1 == reflect.Interface || kind1 == reflect.Ptr // wrong
|
||||||
|
// if kind1 == reflect.Interface || kind1 == reflect.Ptr // wrong
|
||||||
|
// for kind1 == reflect.Interface // wrong
|
||||||
|
if kind1 == reflect.Interface {
|
||||||
value1 = value1.Elem() // un-nest one interface
|
value1 = value1.Elem() // un-nest one interface
|
||||||
kind1 = value1.Kind()
|
kind1 = value1.Kind()
|
||||||
}
|
}
|
||||||
for kind2 == reflect.Interface || kind2 == reflect.Ptr {
|
|
||||||
|
// This second block is identical, but it's just accidentally
|
||||||
|
// symmetrical. The types of input structs are different shapes.
|
||||||
|
// for kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
|
||||||
|
// if kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
|
||||||
|
// for kind2 == reflect.Interface // wrong
|
||||||
|
if kind2 == reflect.Interface {
|
||||||
value2 = value2.Elem() // un-nest one interface
|
value2 = value2.Elem() // un-nest one interface
|
||||||
kind2 = value2.Kind()
|
kind2 = value2.Kind()
|
||||||
}
|
}
|
||||||
@@ -180,20 +203,19 @@ func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[str
|
|||||||
// obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
|
// obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// i think we probably want the same kind, at least for now...
|
// Skip this check in favour of the more complex Into() below...
|
||||||
if kind1 != kind2 {
|
//if kind1 != kind2 {
|
||||||
e := fmt.Errorf("send/recv kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
|
// e := fmt.Errorf("send/recv kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
|
||||||
err = errwrap.Append(err, e) // list of errors
|
// err = errwrap.Append(err, e) // list of errors
|
||||||
continue
|
// continue
|
||||||
}
|
//}
|
||||||
|
|
||||||
// if the types don't match, we can't use send->recv
|
// Skip this check in favour of the more complex Into() below...
|
||||||
// FIXME: do we want to relax this for string -> *string ?
|
//if e := TypeCmp(value1, value2); e != nil {
|
||||||
if e := TypeCmp(value1, value2); e != nil {
|
// e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res)
|
||||||
e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res)
|
// err = errwrap.Append(err, e) // list of errors
|
||||||
err = errwrap.Append(err, e) // list of errors
|
// continue
|
||||||
continue
|
//}
|
||||||
}
|
|
||||||
|
|
||||||
// if we can't set, then well this is pointless!
|
// if we can't set, then well this is pointless!
|
||||||
if !dest.CanSet() {
|
if !dest.CanSet() {
|
||||||
@@ -230,7 +252,8 @@ func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[str
|
|||||||
|
|
||||||
// mutate the struct field dest with the mcl data in fv
|
// mutate the struct field dest with the mcl data in fv
|
||||||
if e := types.Into(fv, dest); e != nil {
|
if e := types.Into(fv, dest); e != nil {
|
||||||
e := errwrap.Wrapf(e, "bad dest %s.%s", v.Res, v.Key)
|
// runtime error, probably from using value res
|
||||||
|
e := errwrap.Wrapf(e, "mismatch: %s.%s (%s) -> %s.%s (%s)", v.Res, v.Key, kind1, res, k, kind2)
|
||||||
err = errwrap.Append(err, e) // list of errors
|
err = errwrap.Append(err, e) // list of errors
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,16 +192,16 @@ func StructFieldCompat(st1 interface{}, key1 string, st2 interface{}, key2 strin
|
|||||||
return fmt.Errorf("can't interface the recv")
|
return fmt.Errorf("can't interface the recv")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're sending _from_ an interface...
|
// If we're sending _from_ an interface... (value res `any` field)
|
||||||
if kind1 == reflect.Interface || kind1 == reflect.Ptr {
|
if kind1 == reflect.Interface || kind1 == reflect.Ptr {
|
||||||
// TODO: Can we do more checks instead of only returning early?
|
// TODO: Can we do more checks instead of only returning early?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// If we're sending _to_ an interface...
|
// If we're sending _to_ an interface... (value res `any` field)
|
||||||
//if kind2 == reflect.Interface {
|
if kind2 == reflect.Interface || kind2 == reflect.Ptr {
|
||||||
// // TODO: Can we do more checks instead of only returning early?
|
// TODO: Can we do more checks instead of only returning early?
|
||||||
// return nil
|
return nil
|
||||||
//}
|
}
|
||||||
|
|
||||||
if kind1 != kind2 {
|
if kind1 != kind2 {
|
||||||
return fmt.Errorf("field kind mismatch between %s and %s", kind1, kind2)
|
return fmt.Errorf("field kind mismatch between %s and %s", kind1, kind2)
|
||||||
|
|||||||
31
lang/interpret_test/TestAstFunc3/send-recv-0.txtar
Normal file
31
lang/interpret_test/TestAstFunc3/send-recv-0.txtar
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
exec "exec0" {
|
||||||
|
cmd => "echo this is stdout && echo this is stderr 1>&2", # to stdout && stderr
|
||||||
|
shell => "/bin/bash",
|
||||||
|
}
|
||||||
|
|
||||||
|
file ["/tmp/command-output", "/tmp/command-stdout", "/tmp/command-stderr",] {
|
||||||
|
state => $const.res.file.state.exists,
|
||||||
|
}
|
||||||
|
|
||||||
|
Exec["exec0"].stdout -> File["/tmp/command-stdout"].content
|
||||||
|
Exec["exec0"].stderr -> File["/tmp/command-stderr"].content
|
||||||
|
# XXX: The Content line can be swapped randomly, create test to allow either.
|
||||||
|
# XXX: In the meantime, we skip including it.
|
||||||
|
#Exec["exec0"].output -> File["/tmp/command-output"].content
|
||||||
|
# Field: file[/tmp/command-output].Content = "this is stdout\nthis is stderr\n"
|
||||||
|
# Field: file[/tmp/command-output].Content = "this is stderr\nthis is stdout\n"
|
||||||
|
-- OUTPUT --
|
||||||
|
Edge: exec[exec0] -> file[/tmp/command-stderr] # exec[exec0] -> file[/tmp/command-stderr]
|
||||||
|
Edge: exec[exec0] -> file[/tmp/command-stdout] # exec[exec0] -> file[/tmp/command-stdout]
|
||||||
|
Field: exec[exec0].Cmd = "echo this is stdout && echo this is stderr 1>&2"
|
||||||
|
Field: exec[exec0].Shell = "/bin/bash"
|
||||||
|
Field: file[/tmp/command-output].State = "exists"
|
||||||
|
Field: file[/tmp/command-stderr].Content = "this is stderr\n"
|
||||||
|
Field: file[/tmp/command-stderr].State = "exists"
|
||||||
|
Field: file[/tmp/command-stdout].Content = "this is stdout\n"
|
||||||
|
Field: file[/tmp/command-stdout].State = "exists"
|
||||||
|
Vertex: exec[exec0]
|
||||||
|
Vertex: file[/tmp/command-output]
|
||||||
|
Vertex: file[/tmp/command-stderr]
|
||||||
|
Vertex: file[/tmp/command-stdout]
|
||||||
18
lang/interpret_test/TestAstFunc3/send-recv-1.txtar
Normal file
18
lang/interpret_test/TestAstFunc3/send-recv-1.txtar
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
test "send" {
|
||||||
|
sendvalue => "this is hello", # sends on key of `hello`
|
||||||
|
# we also secret send on key of `answer` the int value of 42
|
||||||
|
|
||||||
|
Meta:autogroup => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
test "recv" {
|
||||||
|
expectrecv => ["anotherstr",], # expecting to recv on these keys!
|
||||||
|
|
||||||
|
Meta:autogroup => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
# must error, incompatible types
|
||||||
|
Test["send"].answer -> Test["recv"].anotherstr
|
||||||
|
-- OUTPUT --
|
||||||
|
# err: errUnify: cannot send/recv from test[send].answer to test[recv].anotherstr: field kind mismatch between int and string
|
||||||
32
lang/interpret_test/TestAstFunc3/sendrecv-simple1.txtar
Normal file
32
lang/interpret_test/TestAstFunc3/sendrecv-simple1.txtar
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
# send/recv of value1.any into test.msg works!
|
||||||
|
value "value1" {
|
||||||
|
any => "i am value1",
|
||||||
|
}
|
||||||
|
test "test1" {
|
||||||
|
sendvalue => "hello from test",
|
||||||
|
|
||||||
|
Meta:autogroup => false,
|
||||||
|
}
|
||||||
|
value "value2" {
|
||||||
|
any => "", # gets value from send_value above
|
||||||
|
}
|
||||||
|
value "value3" {
|
||||||
|
any => 0, # gets 42
|
||||||
|
}
|
||||||
|
Value["value1"].any -> Test["test1"].anotherstr
|
||||||
|
Test["test1"].hello -> Value["value2"].any
|
||||||
|
Test["test1"].answer -> Value["value3"].any
|
||||||
|
-- OUTPUT --
|
||||||
|
Edge: test[test1] -> value[value2] # test[test1] -> value[value2]
|
||||||
|
Edge: test[test1] -> value[value3] # test[test1] -> value[value3]
|
||||||
|
Edge: value[value1] -> test[test1] # value[value1] -> test[test1]
|
||||||
|
Field: test[test1].AnotherStr = "i am value1"
|
||||||
|
Field: test[test1].SendValue = "hello from test"
|
||||||
|
Field: value[value1].Any = "i am value1"
|
||||||
|
Field: value[value2].Any = "hello from test"
|
||||||
|
Field: value[value3].Any = 42
|
||||||
|
Vertex: test[test1]
|
||||||
|
Vertex: value[value1]
|
||||||
|
Vertex: value[value2]
|
||||||
|
Vertex: value[value3]
|
||||||
15
lang/interpret_test/TestAstFunc3/sendrecv-simple2.txtar
Normal file
15
lang/interpret_test/TestAstFunc3/sendrecv-simple2.txtar
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
# send/recv of value1.any into test.msg works!
|
||||||
|
value "value1" {
|
||||||
|
any => "i am value1",
|
||||||
|
}
|
||||||
|
print "print1" {
|
||||||
|
#msg => "", # gets value from send_value above
|
||||||
|
}
|
||||||
|
Value["value1"].any -> Print["print1"].msg
|
||||||
|
-- OUTPUT --
|
||||||
|
Edge: value[value1] -> print[print1] # value[value1] -> print[print1]
|
||||||
|
Field: print[print1].Msg = "i am value1"
|
||||||
|
Field: value[value1].Any = "i am value1"
|
||||||
|
Vertex: print[print1]
|
||||||
|
Vertex: value[value1]
|
||||||
44
lang/interpret_test/TestAstFunc3/sendrecv-simple3.txtar
Normal file
44
lang/interpret_test/TestAstFunc3/sendrecv-simple3.txtar
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
# send/recv of value1.any into test.msg works!
|
||||||
|
value "value1" {
|
||||||
|
any => "i am value1",
|
||||||
|
}
|
||||||
|
test "test1" {
|
||||||
|
sendvalue => "hello from test",
|
||||||
|
|
||||||
|
Meta:autogroup => false,
|
||||||
|
}
|
||||||
|
value "value2" {
|
||||||
|
any => "", # gets value from send_value above
|
||||||
|
}
|
||||||
|
value "value3" {
|
||||||
|
# XXX: This works because this value gets overwritten, but it really
|
||||||
|
# should get caught at type unification if possible. It is caught at
|
||||||
|
# runtime and we allow it for now since we want the escape hatch with
|
||||||
|
# the `any` types for the moment. The error looks like:
|
||||||
|
# print[print1]: Error: could not SendRecv:
|
||||||
|
# mismatch: value[value3].any (ptr) -> print[print1].msg (string):
|
||||||
|
# cannot Into() 42 of type int into string
|
||||||
|
any => "NOPE", # gets 42
|
||||||
|
}
|
||||||
|
print "print1" {}
|
||||||
|
|
||||||
|
Value["value1"].any -> Test["test1"].anotherstr
|
||||||
|
Test["test1"].hello -> Value["value2"].any
|
||||||
|
Test["test1"].answer -> Value["value3"].any
|
||||||
|
Value["value3"].any -> Print["print1"].msg
|
||||||
|
-- OUTPUT --
|
||||||
|
Edge: test[test1] -> value[value2] # test[test1] -> value[value2]
|
||||||
|
Edge: test[test1] -> value[value3] # test[test1] -> value[value3]
|
||||||
|
Edge: value[value1] -> test[test1] # value[value1] -> test[test1]
|
||||||
|
Edge: value[value3] -> print[print1] # value[value3] -> print[print1]
|
||||||
|
Field: test[test1].AnotherStr = "i am value1"
|
||||||
|
Field: test[test1].SendValue = "hello from test"
|
||||||
|
Field: value[value1].Any = "i am value1"
|
||||||
|
Field: value[value2].Any = "hello from test"
|
||||||
|
Field: value[value3].Any = 42
|
||||||
|
Vertex: print[print1]
|
||||||
|
Vertex: test[test1]
|
||||||
|
Vertex: value[value1]
|
||||||
|
Vertex: value[value2]
|
||||||
|
Vertex: value[value3]
|
||||||
Reference in New Issue
Block a user