// Copyright 2016 The TensorFlow Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tensorflow import ( "fmt" "runtime" "runtime/debug" "testing" ) // createGraphAndOp creates an Operation but loses the reference to the Graph. func createGraphAndOp() (*Operation, error) { t, err := NewTensor(int64(1)) if err != nil { return nil, err } g := NewGraph() output, err := Placeholder(g, "my_placeholder", t.DataType()) if err != nil { return nil, err } return output.Op, nil } func TestOperationLifetime(t *testing.T) { // Ensure that the Graph is not garbage collected while the program // still has access to the Operation. op, err := createGraphAndOp() if err != nil { t.Fatal(err) } forceGC() if got, want := op.Name(), "my_placeholder"; got != want { t.Errorf("Got '%s', want '%s'", got, want) } if got, want := op.Type(), "Placeholder"; got != want { t.Errorf("Got '%s', want '%s'", got, want) } } func TestOperationOutputListSize(t *testing.T) { graph := NewGraph() c1, err := Const(graph, "c1", int64(1)) if err != nil { t.Fatal(err) } c2, err := Const(graph, "c2", [][]int64{{1, 2}, {3, 4}}) if err != nil { t.Fatal(err) } // The ShapeN op takes a list of tensors as input and a list as output. op, err := graph.AddOperation(OpSpec{ Type: "ShapeN", Input: []Input{OutputList{c1, c2}}, }) if err != nil { t.Fatal(err) } n, err := op.OutputListSize("output") if err != nil { t.Fatal(err) } if got, want := n, 2; got != want { t.Errorf("Got %d, want %d", got, want) } if got, want := op.NumOutputs(), 2; got != want { t.Errorf("Got %d, want %d", got, want) } } func TestOperationShapeAttribute(t *testing.T) { g := NewGraph() _, err := g.AddOperation(OpSpec{ Type: "Placeholder", Attrs: map[string]interface{}{ "dtype": Float, "shape": MakeShape(-1, 3), }, }) if err != nil { t.Fatal(err) } // If and when the API to get attributes is added, check that here. } func TestOutputDataTypeAndShape(t *testing.T) { graph := NewGraph() testdata := []struct { Value interface{} Shape []int64 dtype DataType }{ { // Scalar int64(0), []int64{}, Int64, }, { // Vector []int32{1, 2, 3}, []int64{3}, Int32, }, { // Matrix [][]float64{ {1, 2, 3}, {4, 5, 6}, }, []int64{2, 3}, Double, }, } for idx, test := range testdata { t.Run(fmt.Sprintf("#%d Value %T", idx, test.Value), func(t *testing.T) { c, err := Const(graph, fmt.Sprintf("const%d", idx), test.Value) if err != nil { t.Fatal(err) } if got, want := c.DataType(), test.dtype; got != want { t.Errorf("Got DataType %v, want %v", got, want) } shape := c.Shape() if got, want := shape.NumDimensions(), len(test.Shape); got != want { t.Fatalf("Got a shape with %d dimensions, want %d", got, want) } for i := 0; i < len(test.Shape); i++ { if got, want := shape.Size(i), test.Shape[i]; got != want { t.Errorf("Got %d, want %d for dimension #%d/%d", got, want, i, len(test.Shape)) } } }) } // Unknown number of dimensions dummyTensor, err := NewTensor(float64(0)) if err != nil { t.Fatal(err) } placeholder, err := Placeholder(graph, "placeholder", dummyTensor.DataType()) if err != nil { t.Fatal(err) } if shape := placeholder.Shape(); shape.NumDimensions() != -1 { t.Errorf("Got shape %v, wanted an unknown number of dimensions", shape) } } func forceGC() { var mem runtime.MemStats runtime.ReadMemStats(&mem) // It was empirically observed that without this extra allocation // TestOperationLifetime would fail only 50% of the time if // Operation did not hold on to a reference to Graph. With this // additional allocation, and with the bug where Operation does // not hold onto a Graph, the test failed 90+% of the time. // // The author is aware that this technique is potentially fragile // and fishy. Suggestions for alternatives are welcome. bytesTillGC := mem.NextGC - mem.HeapAlloc + 1 _ = make([]byte, bytesTillGC) runtime.GC() debug.FreeOSMemory() }