Compare commits
3 Commits
a9d5bb83a2
...
8a0ae9b03f
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a0ae9b03f | |||
| 9bc90b0e17 | |||
| c70c23d137 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/generate/test
|
||||||
@ -10,7 +10,7 @@ import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|||||||
|
|
||||||
const imports =
|
const imports =
|
||||||
`
|
`
|
||||||
import "git.teibibyte.media/sashakoshka/hopp/tape"
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
`
|
`
|
||||||
|
|
||||||
const preamble = `
|
const preamble = `
|
||||||
|
|||||||
236
generate/generate_test.go
Normal file
236
generate/generate_test.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
// import "fmt"
|
||||||
|
import "strings"
|
||||||
|
import "testing"
|
||||||
|
import "git.tebibyte.media/sashakoshka/goparse"
|
||||||
|
|
||||||
|
var testGenerateCorrect =
|
||||||
|
`package protocol
|
||||||
|
|
||||||
|
/* # Do not edit this package by hand!
|
||||||
|
*
|
||||||
|
* This file was automatically generated by the Holanet PDL compiler. The
|
||||||
|
* source file is located at input.pdl
|
||||||
|
* Please edit that file instead, and re-compile it to this location.
|
||||||
|
*
|
||||||
|
* HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
|
|
||||||
|
// Table is a KTV table with an undefined schema.
|
||||||
|
type Table map[uint16] any
|
||||||
|
|
||||||
|
// Message is any message that can be sent along this protocol.
|
||||||
|
type Message interface {
|
||||||
|
tape.Encodable
|
||||||
|
tape.Decodable
|
||||||
|
|
||||||
|
// Method returns the method code of the message.
|
||||||
|
Method() uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents the protocol data type User.
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Bio string
|
||||||
|
Followers uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeValue encodes the value of this type without the tag. The value is
|
||||||
|
// encoded according to the parameters specified by the tag, if possible.
|
||||||
|
func (this *User) EncodeValue(encoder *tape.Encoder) (n int, err error) {
|
||||||
|
nn, err := tape.WriteTableHeader(2)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := encoder.WriteUint16(0x0000)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := tape.WriteString(encoder, this.Name)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := encoder.WriteUint16(0x0001)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := tape.WriteString(encoder, this.Bio)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode replaces the data in this User with information from the decoder.
|
||||||
|
func (this *User) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||||
|
pull, nn, err := tape.ReadTableHeader(decoder)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
|
for {
|
||||||
|
key, tag, end, nn, err := pull()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if end { break }
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case 0x0000:
|
||||||
|
value, nn, err := tape.ReadString(decoder)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
this.Name = value
|
||||||
|
case 0x0001:
|
||||||
|
value, nn, err := tape.ReadString(decoder)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
this.Bio = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageConnect represents the protocol message M0000 Connect.
|
||||||
|
type MessageConnect struct {
|
||||||
|
Name string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the method code, M0000.
|
||||||
|
func (this *MessageConnect) Method() uint16 {
|
||||||
|
return 0x0000
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes the message to the encoder.
|
||||||
|
func (this *MessageConnect) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||||
|
nn, err := tape.WriteTableHeader(2)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := encoder.WriteUint16(0x0000)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := tape.WriteString(encoder, this.Name)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := encoder.WriteUint16(0x0001)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err := tape.WriteString(encoder, this.Password)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode replaces the data in this message with information from the decoder.
|
||||||
|
func (this *MessageConnect) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||||
|
pull, nn, err := tape.ReadTableHeader(decoder)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
|
for {
|
||||||
|
key, tag, end, nn, err := pull()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if end { break }
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case 0x0000:
|
||||||
|
value, nn, err := tape.ReadString(decoder)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
this.Name = value
|
||||||
|
case 0x0001:
|
||||||
|
value, nn, err := tape.ReadString(decoder)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
this.Password = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageUserList represents the protocol message M0001 UserList.
|
||||||
|
type MessageUserList struct {
|
||||||
|
Users []User
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the method code, M0001.
|
||||||
|
func (this *MessageUserList) Method() uint16 {
|
||||||
|
return 0x0001
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO methods
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestGenerate(test *testing.T) {
|
||||||
|
protocol := defaultProtocol()
|
||||||
|
protocol.Messages[0x0000] = Message {
|
||||||
|
Name: "Connect",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
||||||
|
0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0001] = Message {
|
||||||
|
Name: "UserList",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
protocol.Types["User"] = TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
||||||
|
0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } },
|
||||||
|
0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
correct := testGenerateCorrect
|
||||||
|
|
||||||
|
builder := strings.Builder { }
|
||||||
|
generator := Generator { Output: &builder }
|
||||||
|
/* TODO test n: */ _, err := generator.Generate(&protocol)
|
||||||
|
if err != nil { test.Fatal(parse.Format(err)) }
|
||||||
|
got := builder.String()
|
||||||
|
|
||||||
|
test.Log("CORRECT:")
|
||||||
|
test.Log(correct)
|
||||||
|
test.Log("GOT:")
|
||||||
|
test.Log(got)
|
||||||
|
|
||||||
|
if correct != got {
|
||||||
|
test.Error("not equal")
|
||||||
|
for index := range min(len(correct), len(got)) {
|
||||||
|
if correct[index] == got[index] { continue }
|
||||||
|
test.Log("C:", correct[max(0, index - 8):min(len(correct), index + 8)])
|
||||||
|
test.Log("G:", got[max(0, index - 8):min(len(got), index + 8)])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
test.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateRun(test *testing.T) {
|
||||||
|
protocol := defaultProtocol()
|
||||||
|
protocol.Messages[0x0000] = Message {
|
||||||
|
Name: "Connect",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
||||||
|
0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0001] = Message {
|
||||||
|
Name: "UserList",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
protocol.Types["User"] = TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
||||||
|
0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } },
|
||||||
|
0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testGenerateRun(test, &protocol, `
|
||||||
|
// imports
|
||||||
|
`, `
|
||||||
|
// test case
|
||||||
|
messageConnect := MessageConnect {
|
||||||
|
Name: "rarity",
|
||||||
|
Password: "gems",
|
||||||
|
}
|
||||||
|
testEncode(
|
||||||
|
messageConnect,
|
||||||
|
0x0) // TODO
|
||||||
|
`)
|
||||||
|
}
|
||||||
@ -7,10 +7,14 @@ import "testing"
|
|||||||
import "path/filepath"
|
import "path/filepath"
|
||||||
|
|
||||||
func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCase string) {
|
func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCase string) {
|
||||||
// open files
|
// reset data directory
|
||||||
dir, err := os.MkdirTemp(os.TempDir(), "hopp-generate-test-*")
|
dir := "test/generate-run"
|
||||||
|
err := os.RemoveAll(dir)
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
defer os.RemoveAll(dir)
|
err = os.MkdirAll(dir, 0750)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
|
||||||
|
// open files
|
||||||
sourceFile, err := os.Create(filepath.Join(dir, "protocol.go"))
|
sourceFile, err := os.Create(filepath.Join(dir, "protocol.go"))
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
defer sourceFile.Close()
|
defer sourceFile.Close()
|
||||||
@ -29,15 +33,37 @@ func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCa
|
|||||||
// build static source files
|
// build static source files
|
||||||
imports = `
|
imports = `
|
||||||
import "log"
|
import "log"
|
||||||
|
import "bytes"
|
||||||
|
import "slices"
|
||||||
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
` + imports
|
` + imports
|
||||||
setup := `log.Println("*** BEGIN TEST CASE OUTPUT ***")`
|
setup := `log.Println("*** BEGIN TEST CASE OUTPUT ***")`
|
||||||
teardown := `log.Println("--- END TEST CASE OUTPUT ---")`
|
teardown := `log.Println("--- END TEST CASE OUTPUT ---")`
|
||||||
|
static := `
|
||||||
|
func testEncode(message Message, correct ...byte) {
|
||||||
|
buffer := bytes.Buffer { }
|
||||||
|
encoder := tape.NewEncoder(&buffer)
|
||||||
|
n, err := message.Encode(encoder)
|
||||||
|
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
||||||
|
got := buffer.Bytes()
|
||||||
|
if n != len(got) {
|
||||||
|
log.Fatalln("len incorrect: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got, correct) {
|
||||||
|
log.Fatalln("not equal:")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
fmt.Fprintf(
|
fmt.Fprintf(
|
||||||
mainFile, "package main\n%s\nfunc main() {%s\n%s\n%s\n}\n",
|
mainFile, "package main\n%s\nfunc main() {\n%s\n%s\n%s\n}\n%s",
|
||||||
imports, setup, testCase, teardown)
|
imports, setup, testCase, teardown, static)
|
||||||
|
|
||||||
// build and run test
|
// build and run test
|
||||||
command := exec.Command("go", "run", filepath.Join(dir, "main.go"))
|
command := exec.Command("go", "run", "./generate/test/generate-run")
|
||||||
|
workingDirAbs, err := filepath.Abs("..")
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
command.Dir = workingDirAbs
|
||||||
|
command.Env = os.Environ()
|
||||||
output, err := command.CombinedOutput()
|
output, err := command.CombinedOutput()
|
||||||
test.Logf("output of %v:\n%s", command, output)
|
test.Logf("output of %v:\n%s", command, output)
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user