Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
444 changes: 444 additions & 0 deletions .github/workflows/go-release-ci.yml

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions go/examples/basic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package main

import (
"context"
"errors"
"flag"
"fmt"
"io"
"math"
"os"
"runtime/debug"

ort "github.com/microsoft/onnxruntime/go/onnxruntime"
)

const modulePath = "github.com/microsoft/onnxruntime/go"

func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run(args []string, stdout, stderr io.Writer) error {
flags := flag.NewFlagSet("basic", flag.ContinueOnError)
flags.SetOutput(stderr)
expectedVersion := flags.String("expect-runtime", "", "expected initialized ORT runtime version")
expectedAPI := flags.Int("expect-api", 0, "expected negotiated ORT API version (0 disables the check)")
expectedModule := flags.String("expect-module-version", "", "expected SDK dependency version; omit when the SDK is the main module")
flags.Usage = func() {
_, _ = fmt.Fprintln(stderr, "Usage: basic [flags] MODEL_PATH\nRequires ORT_LIB_PATH. In-checkout runs omit -expect-module-version because the SDK is the main module.")
flags.PrintDefaults()
}
if err := flags.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
if *expectedAPI < 0 {
return fmt.Errorf("expect-api must be nonnegative")
}
if flags.NArg() != 1 || flags.Arg(0) == "" {
return fmt.Errorf("require one model path")
}
path := os.Getenv("ORT_LIB_PATH")
if path == "" {
return fmt.Errorf("ORT_LIB_PATH must be nonempty")
}
if *expectedModule != "" {
info, ok := debug.ReadBuildInfo()
if !ok {
return fmt.Errorf("module build info unavailable")
}
if err := checkBuildInfo(info, *expectedModule); err != nil {
return err
}
}
ort.SetSharedLibraryPath(path)
var version string
var api int
var values []float32
err := func() (err error) {
if err = ort.Init(); err != nil {
return err
}
defer func() { err = errors.Join(err, ort.Shutdown()) }()
closeResource := func(resource io.Closer, name string) {
if closeErr := resource.Close(); closeErr != nil {
err = errors.Join(err, fmt.Errorf("close %s: %w", name, closeErr))
}
}
version, err = ort.GetVersion()
if err != nil {
return err
}
api = ort.APIVersion()
if err = checkRuntime(version, api, *expectedVersion, *expectedAPI); err != nil {
return err
}
session, err := ort.NewSession(flags.Arg(0), nil)
if err != nil {
return err
}
defer closeResource(session, "session")
a, err := ort.CreateTensor[float32]([]int64{2, 3}, []float32{1, 2, 3, 4, 5, 6})
if err != nil {
return err
}
defer closeResource(a, "input A")
b, err := ort.CreateTensor[float32]([]int64{2, 3}, []float32{10, 20, 30, 40, 50, 60})
if err != nil {
return err
}
defer closeResource(b, "input B")
outputs, err := session.Run(context.Background(), map[string]*ort.Tensor{"A": a, "B": b}, nil)
if err != nil {
return err
}
defer func() {
for name, output := range outputs {
closeResource(output, "output "+name)
}
}()
output, ok := outputs["C"]
if len(outputs) != 1 || !ok {
return fmt.Errorf("require exactly output C, got %d outputs", len(outputs))
}
data, err := ort.TensorData[float32](output)
if err != nil {
return err
}
if err = checkOutput(output.Shape(), data); err != nil {
return err
}
// Copy before closing the output tensor, which owns the returned data.
values = append([]float32(nil), data...)
return nil
}()
if err != nil {
return err
}
_, err = fmt.Fprintf(stdout, "ORT version: %s, API version: %d\nC: %v\nShutdown successful\n", version, api, values)
return err
}

func checkOutput(shape []int64, values []float32) error {
if len(shape) != 2 || shape[0] != 2 || shape[1] != 3 {
return fmt.Errorf("output C shape: expected [2 3], got %v", shape)
}
expected := []float32{11, 22, 33, 44, 55, 66}
if len(values) != len(expected) {
return fmt.Errorf("output C length: expected %d, got %d", len(expected), len(values))
}
for i, value := range values {
v := float64(value)
if math.IsNaN(v) || math.IsInf(v, 0) || math.Abs(v-float64(expected[i])) > 1e-6 {
return fmt.Errorf("output C[%d]: expected %g, got %g", i, expected[i], value)
}
}
return nil
}

func checkRuntime(version string, api int, expectedVersion string, expectedAPI int) error {
if expectedVersion != "" && version != expectedVersion {
return fmt.Errorf("runtime mismatch: expected %s, got %s", expectedVersion, version)
}
if expectedAPI != 0 && api != expectedAPI {
return fmt.Errorf("API mismatch: expected %d, got %d", expectedAPI, api)
}
return nil
}

func checkBuildInfo(info *debug.BuildInfo, expectedVersion string) error {
if info == nil {
return fmt.Errorf("module build info unavailable")
}
for _, dependency := range info.Deps {
if dependency.Path != modulePath {
continue
}
if dependency.Replace != nil {
return fmt.Errorf("module %s has a replacement", modulePath)
}
if dependency.Version != expectedVersion {
return fmt.Errorf("module version mismatch: expected %s, got %s", expectedVersion, dependency.Version)
}
return nil
}
return fmt.Errorf("module dependency %s missing from build info", modulePath)
}
130 changes: 130 additions & 0 deletions go/examples/basic/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"bytes"
"fmt"
"math"
"runtime/debug"
"strings"
"testing"
)

func TestCheckOutput(t *testing.T) {
good := []float32{11, 22, 33, 44, 55, 66}
cases := []struct {
name string
shape []int64
values []float32
wantErr bool
}{
{"valid", []int64{2, 3}, good, false},
{"nil shape", nil, good, true},
{"rank", []int64{6}, good, true},
{"dimensions", []int64{3, 2}, good, true},
{"short", []int64{2, 3}, good[:5], true},
{"long", []int64{2, 3}, append(append([]float32{}, good...), 77), true},
}
for i := range good {
for _, bad := range []float32{good[i] + 1, float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1))} {
values := append([]float32{}, good...)
values[i] = bad
cases = append(cases, struct {
name string
shape []int64
values []float32
wantErr bool
}{fmt.Sprintf("value %d %v", i, bad), []int64{2, 3}, values, true})
}
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if err := checkOutput(tc.shape, tc.values); (err != nil) != tc.wantErr {
t.Fatalf("checkOutput() = %v, want error %v", err, tc.wantErr)
}
})
}
}

func TestCheckRuntime(t *testing.T) {
for _, tc := range []struct {
name, version string
api int
expectedVersion string
expectedAPI int
diagnostic string
}{
{"valid", "1.31.0", 27, "1.31.0", 27, ""},
{"optional", "1.31.0", 27, "", 0, ""},
{"runtime", "1.31.0", 27, "0.0.0", 27, "runtime mismatch"},
{"api", "1.31.0", 27, "1.31.0", 28, "API mismatch"},
} {
t.Run(tc.name, func(t *testing.T) {
err := checkRuntime(tc.version, tc.api, tc.expectedVersion, tc.expectedAPI)
if tc.diagnostic == "" {
if err != nil {
t.Fatal(err)
}
} else if err == nil || !strings.Contains(err.Error(), tc.diagnostic) {
t.Fatalf("error = %v, want %q", err, tc.diagnostic)
}
})
}
}

func TestCheckBuildInfo(t *testing.T) {
const version = "v0.0.0-20261008210605-402979ac1bef"
for _, tc := range []struct {
name string
info *debug.BuildInfo
wantErr bool
}{
{"valid", &debug.BuildInfo{Deps: []*debug.Module{{Path: "other", Version: "v1"}, {Path: modulePath, Version: version}}}, false},
{"nil", nil, true},
{"missing", &debug.BuildInfo{}, true},
{"main module", &debug.BuildInfo{Main: debug.Module{Path: modulePath, Version: version}}, true},
{"wrong path", &debug.BuildInfo{Deps: []*debug.Module{{Path: modulePath + "/onnxruntime", Version: version}}}, true},
{"wrong version", &debug.BuildInfo{Deps: []*debug.Module{{Path: modulePath, Version: "v1.0.0"}}}, true},
{"empty version", &debug.BuildInfo{Deps: []*debug.Module{{Path: modulePath}}}, true},
{"replacement", &debug.BuildInfo{Deps: []*debug.Module{{Path: modulePath, Version: version, Replace: &debug.Module{Path: "../local"}}}}, true},
} {
t.Run(tc.name, func(t *testing.T) {
if err := checkBuildInfo(tc.info, version); (err != nil) != tc.wantErr {
t.Fatalf("checkBuildInfo() = %v, want error %v", err, tc.wantErr)
}
})
}
}

func TestRunArguments(t *testing.T) {
t.Setenv("ORT_LIB_PATH", "")
for _, tc := range []struct {
name string
args []string
diagnostic string
}{
{"unknown", []string{"-unknown"}, "flag provided but not defined"},
{"bad api", []string{"-expect-api", "bad"}, "invalid value"},
{"negative api", []string{"-expect-api", "-1", "model.onnx"}, "expect-api"},
{"missing model", nil, "one model path"},
{"extra model", []string{"a", "b"}, "one model path"},
{"missing library", []string{"model.onnx"}, "ORT_LIB_PATH"},
} {
t.Run(tc.name, func(t *testing.T) {
var stdout, stderr bytes.Buffer
err := run(tc.args, &stdout, &stderr)
if err == nil || !strings.Contains(err.Error(), tc.diagnostic) {
t.Fatalf("error = %v, want %q", err, tc.diagnostic)
}
if stdout.Len() != 0 {
t.Fatalf("unexpected success output %q", stdout.String())
}
})
}
var stdout, stderr bytes.Buffer
if err := run([]string{"-h"}, &stdout, &stderr); err != nil {
t.Fatal(err)
}
if !strings.Contains(stderr.String(), "main module") {
t.Fatalf("usage must explain module flag: %q", stderr.String())
}
}
Loading