Skip to content

Commit

Permalink
capnpc-go: fix remaining references to RemoteName
Browse files Browse the repository at this point in the history
Add tests to dry-run code generation to pick up template errors

Fixes #29
  • Loading branch information
zombiezen committed May 8, 2016
1 parent 8f9cefc commit 9ca17dc
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 15 deletions.
27 changes: 17 additions & 10 deletions capnpc-go/capnpc-go.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ func (g *generator) Capnp() string {
}

// generate produces unformatted Go source code from the nodes defined in it.
func (g *generator) generate(pkg string) []byte {
func (g *generator) generate() []byte {
var out bytes.Buffer
fmt.Fprintf(&out, "package %s\n\n", pkg)
fmt.Fprintf(&out, "package %s\n\n", g.nodes[g.fileID].pkg)
out.WriteString("// AUTO GENERATED - DO NOT EDIT\n\n")
out.WriteString("import (\n")
for _, imp := range g.imports.usedImports() {
Expand Down Expand Up @@ -1042,16 +1042,13 @@ func (es enumString) SliceFor(i int) string {
return fmt.Sprintf("[%d:%d]", n, n+len(es[i]))
}

func generateFile(reqf schema.CodeGeneratorRequest_RequestedFile, nodes nodeMap, opts genoptions) error {
id := reqf.Id()
fname, _ := reqf.Filename()
g := newGenerator(id, nodes, opts)
f := nodes[id]
func (g *generator) defineFile() error {
f := g.nodes[g.fileID]
if f == nil {
return fmt.Errorf("no node in schema matches %#x", id)
return fmt.Errorf("no node in schema matches %#x", g.fileID)
}
if f.pkg == "" {
return fmt.Errorf("missing package annotation for %s", fname)
return errors.New("missing package annotation")
}

for _, n := range f.nodes {
Expand Down Expand Up @@ -1080,6 +1077,16 @@ func generateFile(reqf schema.CodeGeneratorRequest_RequestedFile, nodes nodeMap,
return err
}
}
return nil
}

func generateFile(reqf schema.CodeGeneratorRequest_RequestedFile, nodes nodeMap, opts genoptions) error {
id := reqf.Id()
fname, _ := reqf.Filename()
g := newGenerator(id, nodes, opts)
if err := g.defineFile(); err != nil {
return err
}

if dirPath, _ := filepath.Split(fname); dirPath != "" {
err := os.MkdirAll(dirPath, os.ModePerm)
Expand All @@ -1088,7 +1095,7 @@ func generateFile(reqf schema.CodeGeneratorRequest_RequestedFile, nodes nodeMap,
}
}

unformatted := g.generate(f.pkg)
unformatted := g.generate()
formatted, fmtErr := format.Source(unformatted)
if fmtErr != nil {
formatted = unformatted
Expand Down
48 changes: 46 additions & 2 deletions capnpc-go/capnpc-go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ import (
"zombiezen.com/go/capnproto2/internal/schema"
)

func mustReadTestFile(t *testing.T, name string) []byte {
func readTestFile(name string) ([]byte, error) {
path := filepath.Join("testdata", name)
data, err := ioutil.ReadFile(path)
return ioutil.ReadFile(path)
}

func mustReadTestFile(t *testing.T, name string) []byte {
data, err := readTestFile(name)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -228,6 +232,46 @@ func TestDefineConstNodes(t *testing.T) {
}
}

func TestDefineFile(t *testing.T) {
// Sanity check to make sure schemas don't error on generation.

tests := []struct {
fileID uint64
fname string
opts genoptions
}{
{0xd68755941d99d05e, "scopes.capnp.out", genoptions{promises: true}},
{0xecd50d792c3d9992, "util.capnp.out", genoptions{promises: true}},
}
for _, test := range tests {
data, err := readTestFile(test.fname)
if err != nil {
t.Errorf("reading %s: %v", test.fname, err)
continue
}
msg, err := capnp.Unmarshal(data)
if err != nil {
t.Errorf("Unmarshaling %s: %v", test.fname, err)
continue
}
req, err := schema.ReadRootCodeGeneratorRequest(msg)
if err != nil {
t.Errorf("Reading code generator request %s: %v", test.fname, err)
continue
}
nodes, err := buildNodeMap(req)
if err != nil {
t.Errorf("buildNodeMap %s: %v", test.fname, err)
continue
}
g := newGenerator(test.fileID, nodes, test.opts)
if err := g.defineFile(); err != nil {
t.Errorf("defineFile %s: %v", test.fname, err)
continue
}
}
}

type traceRenderer struct {
renderer
calls []renderCall
Expand Down
2 changes: 1 addition & 1 deletion capnpc-go/templates.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion capnpc-go/templates/interfaceClient
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
type {{.Node.Name}} struct { Client {{.G.Capnp}}.Client }

{{range .Methods -}}
func (c {{$.Node.Name}}) {{.Name|title}}(ctx {{$.G.Imports.Context}}.Context, params func({{$.G.RemoteNodeName .Params $.Node}}) error, opts ...{{$.G.Capnp}}.CallOption) {{$.G.RemoteName .Results $.Node}}_Promise {
func (c {{$.Node.Name}}) {{.Name|title}}(ctx {{$.G.Imports.Context}}.Context, params func({{$.G.RemoteNodeName .Params $.Node}}) error, opts ...{{$.G.Capnp}}.CallOption) {{$.G.RemoteNodeName .Results $.Node}}_Promise {
if c.Client == nil {
return {{$.G.RemoteNodeName .Results $.Node}}_Promise{Pipeline: {{$.G.Capnp}}.NewPipeline({{$.G.Capnp}}.ErrorAnswer({{$.G.Capnp}}.ErrNullClient))}
}
Expand Down
2 changes: 1 addition & 1 deletion capnpc-go/templates/interfaceServer
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func {{.Node.Name}}_Methods(methods []{{.G.Imports.Server}}.Method, s {{.Node.Na
{{template "_interfaceMethod" .}}
},
Impl: func(c {{$.G.Imports.Context}}.Context, opts {{$.G.Capnp}}.CallOptions, p, r {{$.G.Capnp}}.Struct) error {
call := {{$.G.RemoteNodeName .Interface $.Node}}_{{.Name}}{c, opts, {{$.G.RemoteName .Params $.Node}}{Struct: p}, {{$.G.RemoteName .Results $.Node}}{Struct: r} }
call := {{$.G.RemoteNodeName .Interface $.Node}}_{{.Name}}{c, opts, {{$.G.RemoteNodeName .Params $.Node}}{Struct: p}, {{$.G.RemoteNodeName .Results $.Node}}{Struct: r} }
return s.{{.Name|title}}(call)
},
ResultsSize: {{$.G.ObjectSize .Results}},
Expand Down
173 changes: 173 additions & 0 deletions capnpc-go/testdata/util.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Sandstorm - Personal Cloud Sandbox
# Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
# 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.

# Derived from sandstorm at 08fafe55995edfe188777eb00b5a2d1826032c59.
# Using Go annotations.

@0xecd50d792c3d9992;

using Go = import "go.capnp";
$Go.package("util");
$Go.import("zombiezen.com/go/capnproto2/capnpc-go/testdata/util");

using DateInNs = Int64;
using DurationInNs = UInt64;

struct KeyValue {
key @0 :Text;
value @1 :Text;
}

struct LocalizedText {
# Text intended to be displayed to a user. May be localized to multiple languages.
#
# TODO(soon): Maybe instead of packing all translations in here, we should have a message code
# and parameter substitutions, with the (message code, locale) -> text map stored elsewhere?

defaultText @0 :Text;
# What to display if no localization matching the user's preferences is available.

localizations @1 :List(Localization);
# Localized versions of the text.

struct Localization {
locale @0 :Text; # IETF BCP 47 locale, e.g. "en" or "en-US".
text @1 :Text; # Localized text.
}
}

interface Handle {
# Arbitrary handle to some resource provided by the platform. May or may not be persistent,
# depending on the use case.
#
# To "drop" a handle means to discard any references. The purpose of a handle is to detect when
# it has been dropped and to free the underlying resource and cancel any ongoing operation at
# that time.
#
# A handle can be persistent. Once you have called `save()` on it to obtain a SturdyRef, dropping
# the live reference will not cancel the operation. You must drop all live references *and*
# explicitly drop any SturdyRef. Every interface which supports restoring SturdyRefs also
# has a corresponding `drop()` method for this purpose.
#
# Unfortunately, there is no way to ensure that a SturdyRef will eventually be deleted. A grain
# could, for example, call `save()` and then simply fail to store the SturdyRef anywhere, causing
# it to be "leaked" until such a time as the grain itself is destroyed. Or worse, a whole server
# could be destroyed in a fire, leaking all SturdyRefs stored therein forever. Apps implementing
# persistent handles must be designed to account for this, probably by giving the owning user
# a way to inspect incoming references and remove them manually. Sandstorm automatically provides
# such an interface for all apps it hosts.
}

interface ByteStream {
# Represents a destination for a stream of bytes. The bytes are ordered, but boundaries between
# messages are not semantically important.
#
# Streams are push-oriented (traditionally, "output streams") rather than pull-oriented ("input
# streams") because this most easily allows multiple packets to be in-flight at once while
# allowing flow control at either end. If we tried to design a pull-oriented stream, it would
# suffer from problems:
# * If we used a naive read() method that returns a simple data blob, you would need to make
# multiple simultaneous calls to deal with network latency. However, those calls could
# potentially return in the wrong order. Although you could restore order by keeping track of
# the order in which the calls were made, this would be a lot of work, and most callers would
# probably fail to do it.
# * We could instead have a read() method that returns a blob as well as a capability to read the
# next blob. You would then make multiple calls using pipelining. Even in this case, though,
# an unpredictable event loop could schedule a pipelined call's return before the parent call.
# Moreover, the interface would be awkward to use and implement. E.g. what happens if you call
# read() twice on the same capability?

write @0 (data :Data);
# Add bytes.
#
# It's safe to make overlapping calls to `write()`, since Cap'n Proto enforces E-Order and so
# the calls will be delivered in order. However, it is a good idea to limit how much data is
# in-flight at a time, so that it doesn't fill up buffers and block other traffic. On the other
# hand, having only one `write()` in flight at a time will not fully utilize the available
# bandwidth if the connection has any significant latency, so parallelizing a few `write()`s is
# a good idea.
#
# Similarly, the implementation of `ByteStream` can delay returning from `write()` as a way to
# hint to the caller that it should hold off on further writes.

done @1 ();
# Call after the last write to indicate that there is no more data. If the `ByteStream` is
# discarded without a call to `done()`, the callee must assume that an error occurred and that
# the data is incomplete.
#
# This will not return until all bytes are successfully written to their final destination.
# It will throw an exception if any error occurs, including if the total number of bytes written
# did not match `expectSize()`.

expectSize @2 (size :UInt64);
# Optionally called to let the receiver know exactly how much data will be written. This should
# normally be called before the first write(), but if called later, `size` indicates how many
# more bytes to expect _after_ the call. It is an error by the caller if more or fewer bytes are
# actually written before `done()`; this also implies that all calls to `expectSize()` must be
# consistent. The caller will ignore any exceptions thrown from this method, therefore it
# is not necessary for the callee to actually implement it.
}

interface Blob @0xe53527a75d90198f {
# Represents a large byte blob.

getSize @0 () -> (size :UInt64);
# Get the total size of the blob. May block if the blob is still being uploaded and the size is
# not yet known.

writeTo @1 (stream :ByteStream, startAtOffset :UInt64 = 0) -> (handle :Handle);
# Write the contents of the blob to `stream`.

getSlice @2 (offset :UInt64, size :UInt32) -> (data :Data);
# Read a slice of the blob starting at the given offset. `size` cannot be greater than Cap'n
# Proto's limit of 2^29-1, and reasonable servers will likely impose far lower limits. If the
# slice would cross past the end of the blob, it is truncated. Otherwise, `data` is always
# exactly `size` bytes (though the caller should check for security purposes).
#
# One technique that makes a lot of sense is to start off by calling e.g. `getSlice(0, 65536)`.
# If the returned data is less than 65536 bytes then you know you got the whole blob, otherwise
# you may want to switch to `writeTo`.
}

interface Assignable(T) {
# An "assignable" -- a mutable memory cell. Supports subscribing to updates.

get @0 () -> (value :T, setter :Setter);
# The returned setter functions the same as you'd get from `asSetter()` except that it will
# become disconnected the next time the Assignable is set by someone else. Thus, you may use this
# to implement optimistic concurrency control.

asGetter @1 () -> (getter :Getter);
# Return a read-only capability for this assignable, co-hosted with the assignable itself for
# performance. If the assignable is persistent, the getter is as well.

asSetter @2 () -> (setter :Setter);
# Return a write-only capability for this assignable, co-hosted with the assignable itself for
# performance. If the assignable is persistent, the setter is as well.

interface Getter {
get @0 () -> (value :T);

subscribe @1 (setter :Setter) -> (handle :Handle);
# Subscribe to updates. Calls the given setter any time the assignable's value changes. Drop
# the returned handle to stop receiving updates. If `setter` is persistent, `handle` will also
# be persistent.
}

interface Setter {
set @0 (value :T) -> ();
}
}
Binary file added capnpc-go/testdata/util.capnp.out
Binary file not shown.

0 comments on commit 9ca17dc

Please sign in to comment.