Update dependencies
This commit is contained in:
parent
90d0732aab
commit
24fc8f9ef5
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"ImportPath": "github.com/tslocum/AnonIRCd",
|
||||
"GoVersion": "go1.8",
|
||||
"GodepVersion": "v79",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/BurntSushi/toml",
|
||||
"Comment": "v0.3.0",
|
||||
"Rev": "b26d9c308763d68093482582cea63d69be07a0f0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/orcaman/concurrent-map",
|
||||
"Rev": "2ae17bc4c860c83513ee50feb9746f3e50d7515d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/sorcix/irc.v2",
|
||||
"Comment": "v1.1.2-18-gb6c6bfc",
|
||||
"Rev": "b6c6bfcd035c95244a8b50de225b214ab678510a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/sorcix/irc.v2/internal",
|
||||
"Comment": "v1.1.2-18-gb6c6bfc",
|
||||
"Rev": "b6c6bfcd035c95244a8b50de225b214ab678510a"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
|
@ -0,0 +1,27 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/orcaman/concurrent-map"
|
||||
packages = ["."]
|
||||
revision = "2ae17bc4c860c83513ee50feb9746f3e50d7515d"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/sorcix/irc.v2"
|
||||
packages = [".","internal"]
|
||||
revision = "f43cef0f500f6de3fcd1e976fd87610a9e970dbc"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "204144074458006bd79f81651581e678f9dfc09468db8c9faa41833302e67213"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
version = "0.3.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/orcaman/concurrent-map"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/sorcix/irc.v2"
|
|
@ -0,0 +1,61 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
}
|
||||
|
||||
func main() {
|
||||
var config tomlConfig
|
||||
if _, err := toml.DecodeFile("example.toml", &config); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Title: %s\n", config.Title)
|
||||
fmt.Printf("Owner: %s (%s, %s), Born: %s\n",
|
||||
config.Owner.Name, config.Owner.Org, config.Owner.Bio,
|
||||
config.Owner.DOB)
|
||||
fmt.Printf("Database: %s %v (Max conn. %d), Enabled? %v\n",
|
||||
config.DB.Server, config.DB.Ports, config.DB.ConnMax,
|
||||
config.DB.Enabled)
|
||||
for serverName, server := range config.Servers {
|
||||
fmt.Printf("Server: %s (%s, %s)\n", serverName, server.IP, server.DC)
|
||||
}
|
||||
fmt.Printf("Client data: %v\n", config.Clients.Data)
|
||||
fmt.Printf("Client hosts: %v\n", config.Clients.Hosts)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Test file for TOML
|
||||
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
||||
# This part you'll really hate
|
||||
|
||||
[the]
|
||||
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
||||
|
||||
[the.hard]
|
||||
test_array = [ "] ", " # "] # ] There you go, parse this!
|
||||
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
||||
# You didn't think it'd as easy as chucking out the last #, did you?
|
||||
another_test_string = " Same thing, but with a string #"
|
||||
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
||||
# Things will get harder
|
||||
|
||||
[the.hard.bit#]
|
||||
what? = "You don't think some user won't do that?"
|
||||
multi_line_array = [
|
||||
"]",
|
||||
# ] Oh yes I did
|
||||
]
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# [x] you
|
||||
# [x.y] don't
|
||||
# [x.y.z] need these
|
||||
[x.y.z.w] # for this to work
|
|
@ -0,0 +1,6 @@
|
|||
# DO NOT WANT
|
||||
[fruit]
|
||||
type = "apple"
|
||||
|
||||
[fruit.type]
|
||||
apple = "yes"
|
|
@ -0,0 +1,35 @@
|
|||
# This is an INVALID TOML document. Boom.
|
||||
# Can you spot the error without help?
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T7:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
|
@ -0,0 +1,5 @@
|
|||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
|
@ -0,0 +1 @@
|
|||
some_key_NAME = "wat"
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Implements the TOML test suite interface
|
||||
|
||||
This is an implementation of the interface expected by
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for my
|
||||
[toml parser written in Go](https://github.com/BurntSushi/toml).
|
||||
In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
|
||||
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Compatible with `toml-test` version
|
||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
|
@ -0,0 +1,90 @@
|
|||
// Command toml-test-decoder satisfies the toml-test interface for testing
|
||||
// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
var tmp interface{}
|
||||
if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
|
||||
log.Fatalf("Error decoding TOML: %s", err)
|
||||
}
|
||||
|
||||
typedTmp := translate(tmp)
|
||||
if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
|
||||
log.Fatalf("Error encoding JSON: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func translate(tomlData interface{}) interface{} {
|
||||
switch orig := tomlData.(type) {
|
||||
case map[string]interface{}:
|
||||
typed := make(map[string]interface{}, len(orig))
|
||||
for k, v := range orig {
|
||||
typed[k] = translate(v)
|
||||
}
|
||||
return typed
|
||||
case []map[string]interface{}:
|
||||
typed := make([]map[string]interface{}, len(orig))
|
||||
for i, v := range orig {
|
||||
typed[i] = translate(v).(map[string]interface{})
|
||||
}
|
||||
return typed
|
||||
case []interface{}:
|
||||
typed := make([]interface{}, len(orig))
|
||||
for i, v := range orig {
|
||||
typed[i] = translate(v)
|
||||
}
|
||||
|
||||
// We don't really need to tag arrays, but let's be future proof.
|
||||
// (If TOML ever supports tuples, we'll need this.)
|
||||
return tag("array", typed)
|
||||
case time.Time:
|
||||
return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
|
||||
case bool:
|
||||
return tag("bool", fmt.Sprintf("%v", orig))
|
||||
case int64:
|
||||
return tag("integer", fmt.Sprintf("%d", orig))
|
||||
case float64:
|
||||
return tag("float", fmt.Sprintf("%v", orig))
|
||||
case string:
|
||||
return tag("string", orig)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unknown type: %T", tomlData))
|
||||
}
|
||||
|
||||
func tag(typeName string, data interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": typeName,
|
||||
"value": data,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Implements the TOML test suite interface for TOML encoders
|
||||
|
||||
This is an implementation of the interface expected by
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for the
|
||||
[TOML encoder](https://github.com/BurntSushi/toml).
|
||||
In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
|
||||
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Compatible with `toml-test` version
|
||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
|
@ -0,0 +1,131 @@
|
|||
// Command toml-test-encoder satisfies the toml-test interface for testing
|
||||
// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
var tmp interface{}
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
|
||||
log.Fatalf("Error decoding JSON: %s", err)
|
||||
}
|
||||
|
||||
tomlData := translate(tmp)
|
||||
if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
|
||||
log.Fatalf("Error encoding TOML: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func translate(typedJson interface{}) interface{} {
|
||||
switch v := typedJson.(type) {
|
||||
case map[string]interface{}:
|
||||
if len(v) == 2 && in("type", v) && in("value", v) {
|
||||
return untag(v)
|
||||
}
|
||||
m := make(map[string]interface{}, len(v))
|
||||
for k, v2 := range v {
|
||||
m[k] = translate(v2)
|
||||
}
|
||||
return m
|
||||
case []interface{}:
|
||||
tabArray := make([]map[string]interface{}, len(v))
|
||||
for i := range v {
|
||||
if m, ok := translate(v[i]).(map[string]interface{}); ok {
|
||||
tabArray[i] = m
|
||||
} else {
|
||||
log.Fatalf("JSON arrays may only contain objects. This " +
|
||||
"corresponds to only tables being allowed in " +
|
||||
"TOML table arrays.")
|
||||
}
|
||||
}
|
||||
return tabArray
|
||||
}
|
||||
log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func untag(typed map[string]interface{}) interface{} {
|
||||
t := typed["type"].(string)
|
||||
v := typed["value"]
|
||||
switch t {
|
||||
case "string":
|
||||
return v.(string)
|
||||
case "integer":
|
||||
v := v.(string)
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as integer: %s", v, err)
|
||||
}
|
||||
return n
|
||||
case "float":
|
||||
v := v.(string)
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as float64: %s", v, err)
|
||||
}
|
||||
return f
|
||||
case "datetime":
|
||||
v := v.(string)
|
||||
t, err := time.Parse("2006-01-02T15:04:05Z", v)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
|
||||
}
|
||||
return t
|
||||
case "bool":
|
||||
v := v.(string)
|
||||
switch v {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
}
|
||||
log.Fatalf("Could not parse '%s' as a boolean.", v)
|
||||
case "array":
|
||||
v := v.([]interface{})
|
||||
array := make([]interface{}, len(v))
|
||||
for i := range v {
|
||||
if m, ok := v[i].(map[string]interface{}); ok {
|
||||
array[i] = untag(m)
|
||||
} else {
|
||||
log.Fatalf("Arrays may only contain other arrays or "+
|
||||
"primitive values, but found a '%T'.", m)
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
log.Fatalf("Unrecognized tag type '%s'.", t)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func in(key string, m map[string]interface{}) bool {
|
||||
_, ok := m[key]
|
||||
return ok
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# TOML Validator
|
||||
|
||||
If Go is installed, it's simple to try it out:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
You can see the types of every key in a TOML file with:
|
||||
|
||||
```bash
|
||||
tomlv -types some-toml-file.toml
|
||||
```
|
||||
|
||||
At the moment, only one error message is reported at a time. Error messages
|
||||
include line numbers. No output means that the files given are valid TOML, or
|
||||
there is a bug in `tomlv`.
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
@ -0,0 +1,61 @@
|
|||
// Command tomlv validates TOML documents and prints each key's type.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
var (
|
||||
flagTypes = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
|
||||
flag.BoolVar(&flagTypes, "types", flagTypes,
|
||||
"When set, the types of every defined key will be shown.")
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
|
||||
path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if flag.NArg() < 1 {
|
||||
flag.Usage()
|
||||
}
|
||||
for _, f := range flag.Args() {
|
||||
var tmp interface{}
|
||||
md, err := toml.DecodeFile(f, &tmp)
|
||||
if err != nil {
|
||||
log.Fatalf("Error in '%s': %s", f, err)
|
||||
}
|
||||
if flagTypes {
|
||||
printTypes(md)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTypes(md toml.MetaData) {
|
||||
tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
for _, key := range md.Keys() {
|
||||
fmt.Fprintf(tabw, "%s%s\t%s\n",
|
||||
strings.Repeat(" ", len(key)-1), key, md.Type(key...))
|
||||
}
|
||||
tabw.Flush()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,615 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEncodeRoundTrip(t *testing.T) {
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time
|
||||
Ipaddress net.IP
|
||||
}
|
||||
|
||||
var inputs = Config{
|
||||
13,
|
||||
[]string{"one", "two", "three"},
|
||||
3.145,
|
||||
[]int{11, 2, 3, 4},
|
||||
time.Now(),
|
||||
net.ParseIP("192.168.59.254"),
|
||||
}
|
||||
|
||||
var firstBuffer bytes.Buffer
|
||||
e := NewEncoder(&firstBuffer)
|
||||
err := e.Encode(inputs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var outputs Config
|
||||
if _, err := Decode(firstBuffer.String(), &outputs); err != nil {
|
||||
t.Logf("Could not decode:\n-----\n%s\n-----\n",
|
||||
firstBuffer.String())
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// could test each value individually, but I'm lazy
|
||||
var secondBuffer bytes.Buffer
|
||||
e2 := NewEncoder(&secondBuffer)
|
||||
err = e2.Encode(outputs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if firstBuffer.String() != secondBuffer.String() {
|
||||
t.Error(
|
||||
firstBuffer.String(),
|
||||
"\n\n is not identical to\n\n",
|
||||
secondBuffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
// XXX(burntsushi)
|
||||
// I think these tests probably should be removed. They are good, but they
|
||||
// ought to be obsolete by toml-test.
|
||||
func TestEncode(t *testing.T) {
|
||||
type Embedded struct {
|
||||
Int int `toml:"_int"`
|
||||
}
|
||||
type NonStruct int
|
||||
|
||||
date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600))
|
||||
dateStr := "2014-05-11T19:30:40Z"
|
||||
|
||||
tests := map[string]struct {
|
||||
input interface{}
|
||||
wantOutput string
|
||||
wantError error
|
||||
}{
|
||||
"bool field": {
|
||||
input: struct {
|
||||
BoolTrue bool
|
||||
BoolFalse bool
|
||||
}{true, false},
|
||||
wantOutput: "BoolTrue = true\nBoolFalse = false\n",
|
||||
},
|
||||
"int fields": {
|
||||
input: struct {
|
||||
Int int
|
||||
Int8 int8
|
||||
Int16 int16
|
||||
Int32 int32
|
||||
Int64 int64
|
||||
}{1, 2, 3, 4, 5},
|
||||
wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
|
||||
},
|
||||
"uint fields": {
|
||||
input: struct {
|
||||
Uint uint
|
||||
Uint8 uint8
|
||||
Uint16 uint16
|
||||
Uint32 uint32
|
||||
Uint64 uint64
|
||||
}{1, 2, 3, 4, 5},
|
||||
wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
|
||||
"\nUint64 = 5\n",
|
||||
},
|
||||
"float fields": {
|
||||
input: struct {
|
||||
Float32 float32
|
||||
Float64 float64
|
||||
}{1.5, 2.5},
|
||||
wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
|
||||
},
|
||||
"string field": {
|
||||
input: struct{ String string }{"foo"},
|
||||
wantOutput: "String = \"foo\"\n",
|
||||
},
|
||||
"string field and unexported field": {
|
||||
input: struct {
|
||||
String string
|
||||
unexported int
|
||||
}{"foo", 0},
|
||||
wantOutput: "String = \"foo\"\n",
|
||||
},
|
||||
"datetime field in UTC": {
|
||||
input: struct{ Date time.Time }{date},
|
||||
wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
|
||||
},
|
||||
"datetime field as primitive": {
|
||||
// Using a map here to fail if isStructOrMap() returns true for
|
||||
// time.Time.
|
||||
input: map[string]interface{}{
|
||||
"Date": date,
|
||||
"Int": 1,
|
||||
},
|
||||
wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
|
||||
},
|
||||
"array fields": {
|
||||
input: struct {
|
||||
IntArray0 [0]int
|
||||
IntArray3 [3]int
|
||||
}{[0]int{}, [3]int{1, 2, 3}},
|
||||
wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
|
||||
},
|
||||
"slice fields": {
|
||||
input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
|
||||
nil, []int{}, []int{1, 2, 3},
|
||||
},
|
||||
wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
|
||||
},
|
||||
"datetime slices": {
|
||||
input: struct{ DatetimeSlice []time.Time }{
|
||||
[]time.Time{date, date},
|
||||
},
|
||||
wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
|
||||
dateStr, dateStr),
|
||||
},
|
||||
"nested arrays and slices": {
|
||||
input: struct {
|
||||
SliceOfArrays [][2]int
|
||||
ArrayOfSlices [2][]int
|
||||
SliceOfArraysOfSlices [][2][]int
|
||||
ArrayOfSlicesOfArrays [2][][2]int
|
||||
SliceOfMixedArrays [][2]interface{}
|
||||
ArrayOfMixedSlices [2][]interface{}
|
||||
}{
|
||||
[][2]int{{1, 2}, {3, 4}},
|
||||
[2][]int{{1, 2}, {3, 4}},
|
||||
[][2][]int{
|
||||
{
|
||||
{1, 2}, {3, 4},
|
||||
},
|
||||
{
|
||||
{5, 6}, {7, 8},
|
||||
},
|
||||
},
|
||||
[2][][2]int{
|
||||
{
|
||||
{1, 2}, {3, 4},
|
||||
},
|
||||
{
|
||||
{5, 6}, {7, 8},
|
||||
},
|
||||
},
|
||||
[][2]interface{}{
|
||||
{1, 2}, {"a", "b"},
|
||||
},
|
||||
[2][]interface{}{
|
||||
{1, 2}, {"a", "b"},
|
||||
},
|
||||
},
|
||||
wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
|
||||
ArrayOfSlices = [[1, 2], [3, 4]]
|
||||
SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
SliceOfMixedArrays = [[1, 2], ["a", "b"]]
|
||||
ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
|
||||
`,
|
||||
},
|
||||
"empty slice": {
|
||||
input: struct{ Empty []interface{} }{[]interface{}{}},
|
||||
wantOutput: "Empty = []\n",
|
||||
},
|
||||
"(error) slice with element type mismatch (string and integer)": {
|
||||
input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
|
||||
wantError: errArrayMixedElementTypes,
|
||||
},
|
||||
"(error) slice with element type mismatch (integer and float)": {
|
||||
input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
|
||||
wantError: errArrayMixedElementTypes,
|
||||
},
|
||||
"slice with elems of differing Go types, same TOML types": {
|
||||
input: struct {
|
||||
MixedInts []interface{}
|
||||
MixedFloats []interface{}
|
||||
}{
|
||||
[]interface{}{
|
||||
int(1), int8(2), int16(3), int32(4), int64(5),
|
||||
uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
|
||||
},
|
||||
[]interface{}{float32(1.5), float64(2.5)},
|
||||
},
|
||||
wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
|
||||
"MixedFloats = [1.5, 2.5]\n",
|
||||
},
|
||||
"(error) slice w/ element type mismatch (one is nested array)": {
|
||||
input: struct{ Mixed []interface{} }{
|
||||
[]interface{}{1, []interface{}{2}},
|
||||
},
|
||||
wantError: errArrayMixedElementTypes,
|
||||
},
|
||||
"(error) slice with 1 nil element": {
|
||||
input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
|
||||
wantError: errArrayNilElement,
|
||||
},
|
||||
"(error) slice with 1 nil element (and other non-nil elements)": {
|
||||
input: struct{ NilElement []interface{} }{
|
||||
[]interface{}{1, nil},
|
||||
},
|
||||
wantError: errArrayNilElement,
|
||||
},
|
||||
"simple map": {
|
||||
input: map[string]int{"a": 1, "b": 2},
|
||||
wantOutput: "a = 1\nb = 2\n",
|
||||
},
|
||||
"map with interface{} value type": {
|
||||
input: map[string]interface{}{"a": 1, "b": "c"},
|
||||
wantOutput: "a = 1\nb = \"c\"\n",
|
||||
},
|
||||
"map with interface{} value type, some of which are structs": {
|
||||
input: map[string]interface{}{
|
||||
"a": struct{ Int int }{2},
|
||||
"b": 1,
|
||||
},
|
||||
wantOutput: "b = 1\n\n[a]\n Int = 2\n",
|
||||
},
|
||||
"nested map": {
|
||||
input: map[string]map[string]int{
|
||||
"a": {"b": 1},
|
||||
"c": {"d": 2},
|
||||
},
|
||||
wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
|
||||
},
|
||||
"nested struct": {
|
||||
input: struct{ Struct struct{ Int int } }{
|
||||
struct{ Int int }{1},
|
||||
},
|
||||
wantOutput: "[Struct]\n Int = 1\n",
|
||||
},
|
||||
"nested struct and non-struct field": {
|
||||
input: struct {
|
||||
Struct struct{ Int int }
|
||||
Bool bool
|
||||
}{struct{ Int int }{1}, true},
|
||||
wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
|
||||
},
|
||||
"2 nested structs": {
|
||||
input: struct{ Struct1, Struct2 struct{ Int int } }{
|
||||
struct{ Int int }{1}, struct{ Int int }{2},
|
||||
},
|
||||
wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
|
||||
},
|
||||
"deeply nested structs": {
|
||||
input: struct {
|
||||
Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
|
||||
}{
|
||||
struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
|
||||
struct{ Struct3 *struct{ Int int } }{nil},
|
||||
},
|
||||
wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
|
||||
"\n\n[Struct2]\n",
|
||||
},
|
||||
"nested struct with nil struct elem": {
|
||||
input: struct {
|
||||
Struct struct{ Inner *struct{ Int int } }
|
||||
}{
|
||||
struct{ Inner *struct{ Int int } }{nil},
|
||||
},
|
||||
wantOutput: "[Struct]\n",
|
||||
},
|
||||
"nested struct with no fields": {
|
||||
input: struct {
|
||||
Struct struct{ Inner struct{} }
|
||||
}{
|
||||
struct{ Inner struct{} }{struct{}{}},
|
||||
},
|
||||
wantOutput: "[Struct]\n [Struct.Inner]\n",
|
||||
},
|
||||
"struct with tags": {
|
||||
input: struct {
|
||||
Struct struct {
|
||||
Int int `toml:"_int"`
|
||||
} `toml:"_struct"`
|
||||
Bool bool `toml:"_bool"`
|
||||
}{
|
||||
struct {
|
||||
Int int `toml:"_int"`
|
||||
}{1}, true,
|
||||
},
|
||||
wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
|
||||
},
|
||||
"embedded struct": {
|
||||
input: struct{ Embedded }{Embedded{1}},
|
||||
wantOutput: "_int = 1\n",
|
||||
},
|
||||
"embedded *struct": {
|
||||
input: struct{ *Embedded }{&Embedded{1}},
|
||||
wantOutput: "_int = 1\n",
|
||||
},
|
||||
"nested embedded struct": {
|
||||
input: struct {
|
||||
Struct struct{ Embedded } `toml:"_struct"`
|
||||
}{struct{ Embedded }{Embedded{1}}},
|
||||
wantOutput: "[_struct]\n _int = 1\n",
|
||||
},
|
||||
"nested embedded *struct": {
|
||||
input: struct {
|
||||
Struct struct{ *Embedded } `toml:"_struct"`
|
||||
}{struct{ *Embedded }{&Embedded{1}}},
|
||||
wantOutput: "[_struct]\n _int = 1\n",
|
||||
},
|
||||
"embedded non-struct": {
|
||||
input: struct{ NonStruct }{5},
|
||||
wantOutput: "NonStruct = 5\n",
|
||||
},
|
||||
"array of tables": {
|
||||
input: struct {
|
||||
Structs []*struct{ Int int } `toml:"struct"`
|
||||
}{
|
||||
[]*struct{ Int int }{{1}, {3}},
|
||||
},
|
||||
wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
|
||||
},
|
||||
"array of tables order": {
|
||||
input: map[string]interface{}{
|
||||
"map": map[string]interface{}{
|
||||
"zero": 5,
|
||||
"arr": []map[string]int{
|
||||
{
|
||||
"friend": 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
|
||||
},
|
||||
"(error) top-level slice": {
|
||||
input: []struct{ Int int }{{1}, {2}, {3}},
|
||||
wantError: errNoKey,
|
||||
},
|
||||
"(error) slice of slice": {
|
||||
input: struct {
|
||||
Slices [][]struct{ Int int }
|
||||
}{
|
||||
[][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
|
||||
},
|
||||
wantError: errArrayNoTable,
|
||||
},
|
||||
"(error) map no string key": {
|
||||
input: map[int]string{1: ""},
|
||||
wantError: errNonString,
|
||||
},
|
||||
"(error) empty key name": {
|
||||
input: map[string]int{"": 1},
|
||||
wantError: errAnything,
|
||||
},
|
||||
"(error) empty map name": {
|
||||
input: map[string]interface{}{
|
||||
"": map[string]int{"v": 1},
|
||||
},
|
||||
wantError: errAnything,
|
||||
},
|
||||
}
|
||||
for label, test := range tests {
|
||||
encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeNestedTableArrays(t *testing.T) {
|
||||
type song struct {
|
||||
Name string `toml:"name"`
|
||||
}
|
||||
type album struct {
|
||||
Name string `toml:"name"`
|
||||
Songs []song `toml:"songs"`
|
||||
}
|
||||
type springsteen struct {
|
||||
Albums []album `toml:"albums"`
|
||||
}
|
||||
value := springsteen{
|
||||
[]album{
|
||||
{"Born to Run",
|
||||
[]song{{"Jungleland"}, {"Meeting Across the River"}}},
|
||||
{"Born in the USA",
|
||||
[]song{{"Glory Days"}, {"Dancing in the Dark"}}},
|
||||
},
|
||||
}
|
||||
expected := `[[albums]]
|
||||
name = "Born to Run"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Jungleland"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Meeting Across the River"
|
||||
|
||||
[[albums]]
|
||||
name = "Born in the USA"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Glory Days"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Dancing in the Dark"
|
||||
`
|
||||
encodeExpected(t, "nested table arrays", value, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
|
||||
type Alpha struct {
|
||||
V int
|
||||
}
|
||||
type Beta struct {
|
||||
V int
|
||||
}
|
||||
type Conf struct {
|
||||
V int
|
||||
A Alpha
|
||||
B []Beta
|
||||
}
|
||||
|
||||
val := Conf{
|
||||
V: 1,
|
||||
A: Alpha{2},
|
||||
B: []Beta{{3}},
|
||||
}
|
||||
expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
|
||||
encodeExpected(t, "array hash with normal hash order", val, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeWithOmitEmpty(t *testing.T) {
|
||||
type simple struct {
|
||||
Bool bool `toml:"bool,omitempty"`
|
||||
String string `toml:"string,omitempty"`
|
||||
Array [0]byte `toml:"array,omitempty"`
|
||||
Slice []int `toml:"slice,omitempty"`
|
||||
Map map[string]string `toml:"map,omitempty"`
|
||||
}
|
||||
|
||||
var v simple
|
||||
encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
|
||||
v = simple{
|
||||
Bool: true,
|
||||
String: " ",
|
||||
Slice: []int{2, 3, 4},
|
||||
Map: map[string]string{"foo": "bar"},
|
||||
}
|
||||
expected := `bool = true
|
||||
string = " "
|
||||
slice = [2, 3, 4]
|
||||
|
||||
[map]
|
||||
foo = "bar"
|
||||
`
|
||||
encodeExpected(t, "fields with omitempty are not omitted when non-empty",
|
||||
v, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeWithOmitZero(t *testing.T) {
|
||||
type simple struct {
|
||||
Number int `toml:"number,omitzero"`
|
||||
Real float64 `toml:"real,omitzero"`
|
||||
Unsigned uint `toml:"unsigned,omitzero"`
|
||||
}
|
||||
|
||||
value := simple{0, 0.0, uint(0)}
|
||||
expected := ""
|
||||
|
||||
encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
|
||||
|
||||
value.Number = 10
|
||||
value.Real = 20
|
||||
value.Unsigned = 5
|
||||
expected = `number = 10
|
||||
real = 20.0
|
||||
unsigned = 5
|
||||
`
|
||||
encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeOmitemptyWithEmptyName(t *testing.T) {
|
||||
type simple struct {
|
||||
S []int `toml:",omitempty"`
|
||||
}
|
||||
v := simple{[]int{1, 2, 3}}
|
||||
expected := "S = [1, 2, 3]\n"
|
||||
encodeExpected(t, "simple with omitempty, no name, non-empty field",
|
||||
v, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeAnonymousStruct(t *testing.T) {
|
||||
type Inner struct{ N int }
|
||||
type Outer0 struct{ Inner }
|
||||
type Outer1 struct {
|
||||
Inner `toml:"inner"`
|
||||
}
|
||||
|
||||
v0 := Outer0{Inner{3}}
|
||||
expected := "N = 3\n"
|
||||
encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
|
||||
|
||||
v1 := Outer1{Inner{3}}
|
||||
expected = "[inner]\n N = 3\n"
|
||||
encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeAnonymousStructPointerField(t *testing.T) {
|
||||
type Inner struct{ N int }
|
||||
type Outer0 struct{ *Inner }
|
||||
type Outer1 struct {
|
||||
*Inner `toml:"inner"`
|
||||
}
|
||||
|
||||
v0 := Outer0{}
|
||||
expected := ""
|
||||
encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
|
||||
|
||||
v0 = Outer0{&Inner{3}}
|
||||
expected = "N = 3\n"
|
||||
encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
|
||||
|
||||
v1 := Outer1{}
|
||||
expected = ""
|
||||
encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
|
||||
|
||||
v1 = Outer1{&Inner{3}}
|
||||
expected = "[inner]\n N = 3\n"
|
||||
encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeIgnoredFields(t *testing.T) {
|
||||
type simple struct {
|
||||
Number int `toml:"-"`
|
||||
}
|
||||
value := simple{}
|
||||
expected := ""
|
||||
encodeExpected(t, "ignored field", value, expected, nil)
|
||||
}
|
||||
|
||||
func encodeExpected(
|
||||
t *testing.T, label string, val interface{}, wantStr string, wantErr error,
|
||||
) {
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
err := enc.Encode(val)
|
||||
if err != wantErr {
|
||||
if wantErr != nil {
|
||||
if wantErr == errAnything && err != nil {
|
||||
return
|
||||
}
|
||||
t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err)
|
||||
} else {
|
||||
t.Errorf("%s: Encode failed: %s", label, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if got := buf.String(); wantStr != got {
|
||||
t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n",
|
||||
label, wantStr, got)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleEncoder_Encode() {
|
||||
date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC")
|
||||
var config = map[string]interface{}{
|
||||
"date": date,
|
||||
"counts": []int{1, 1, 2, 3, 5, 8},
|
||||
"hash": map[string]string{
|
||||
"key1": "val1",
|
||||
"key2": "val2",
|
||||
},
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := NewEncoder(buf).Encode(config); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(buf.String())
|
||||
|
||||
// Output:
|
||||
// counts = [1, 1, 2, 3, 5, 8]
|
||||
// date = 2010-03-14T18:00:00Z
|
||||
//
|
||||
// [hash]
|
||||
// key1 = "val1"
|
||||
// key2 = "val2"
|
||||
}
|
196
vendor/github.com/orcaman/concurrent-map/concurrent_map_bench_test.go
generated
vendored
Normal file
196
vendor/github.com/orcaman/concurrent-map/concurrent_map_bench_test.go
generated
vendored
Normal file
|
@ -0,0 +1,196 @@
|
|||
package cmap
|
||||
|
||||
import "testing"
|
||||
import "strconv"
|
||||
|
||||
func BenchmarkItems(b *testing.B) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 10000; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Items()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalJson(b *testing.B) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 10000; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.MarshalJSON()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStrconv(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strconv.Itoa(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSingleInsertAbsent(b *testing.B) {
|
||||
m := New()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Set(strconv.Itoa(i), "value")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSingleInsertPresent(b *testing.B) {
|
||||
m := New()
|
||||
m.Set("key", "value")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Set("key", "value")
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMultiInsertDifferent(b *testing.B) {
|
||||
m := New()
|
||||
finished := make(chan struct{}, b.N)
|
||||
_, set := GetSet(m, finished)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
set(strconv.Itoa(i), "value")
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-finished
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMultiInsertDifferent_1_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiInsertDifferent, b, 1)
|
||||
}
|
||||
func BenchmarkMultiInsertDifferent_16_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiInsertDifferent, b, 16)
|
||||
}
|
||||
func BenchmarkMultiInsertDifferent_32_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiInsertDifferent, b, 32)
|
||||
}
|
||||
func BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetDifferent, b, 256)
|
||||
}
|
||||
|
||||
func BenchmarkMultiInsertSame(b *testing.B) {
|
||||
m := New()
|
||||
finished := make(chan struct{}, b.N)
|
||||
_, set := GetSet(m, finished)
|
||||
m.Set("key", "value")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
set("key", "value")
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-finished
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMultiGetSame(b *testing.B) {
|
||||
m := New()
|
||||
finished := make(chan struct{}, b.N)
|
||||
get, _ := GetSet(m, finished)
|
||||
m.Set("key", "value")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
get("key", "value")
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-finished
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMultiGetSetDifferent(b *testing.B) {
|
||||
m := New()
|
||||
finished := make(chan struct{}, 2*b.N)
|
||||
get, set := GetSet(m, finished)
|
||||
m.Set("-1", "value")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
set(strconv.Itoa(i-1), "value")
|
||||
get(strconv.Itoa(i), "value")
|
||||
}
|
||||
for i := 0; i < 2*b.N; i++ {
|
||||
<-finished
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMultiGetSetDifferent_1_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetDifferent, b, 1)
|
||||
}
|
||||
func BenchmarkMultiGetSetDifferent_16_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetDifferent, b, 16)
|
||||
}
|
||||
func BenchmarkMultiGetSetDifferent_32_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetDifferent, b, 32)
|
||||
}
|
||||
func BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetDifferent, b, 256)
|
||||
}
|
||||
|
||||
func benchmarkMultiGetSetBlock(b *testing.B) {
|
||||
m := New()
|
||||
finished := make(chan struct{}, 2*b.N)
|
||||
get, set := GetSet(m, finished)
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Set(strconv.Itoa(i%100), "value")
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
set(strconv.Itoa(i%100), "value")
|
||||
get(strconv.Itoa(i%100), "value")
|
||||
}
|
||||
for i := 0; i < 2*b.N; i++ {
|
||||
<-finished
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMultiGetSetBlock_1_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetBlock, b, 1)
|
||||
}
|
||||
func BenchmarkMultiGetSetBlock_16_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetBlock, b, 16)
|
||||
}
|
||||
func BenchmarkMultiGetSetBlock_32_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetBlock, b, 32)
|
||||
}
|
||||
func BenchmarkMultiGetSetBlock_256_Shard(b *testing.B) {
|
||||
runWithShards(benchmarkMultiGetSetBlock, b, 256)
|
||||
}
|
||||
|
||||
func GetSet(m ConcurrentMap, finished chan struct{}) (set func(key, value string), get func(key, value string)) {
|
||||
return func(key, value string) {
|
||||
for i := 0; i < 10; i++ {
|
||||
m.Get(key)
|
||||
}
|
||||
finished <- struct{}{}
|
||||
}, func(key, value string) {
|
||||
for i := 0; i < 10; i++ {
|
||||
m.Set(key, value)
|
||||
}
|
||||
finished <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func runWithShards(bench func(b *testing.B), b *testing.B, shardsCount int) {
|
||||
oldShardsCount := SHARD_COUNT
|
||||
SHARD_COUNT = shardsCount
|
||||
bench(b)
|
||||
SHARD_COUNT = oldShardsCount
|
||||
}
|
||||
|
||||
func BenchmarkKeys(b *testing.B) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 10000; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Keys()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,574 @@
|
|||
package cmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Animal struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func TestMapCreation(t *testing.T) {
|
||||
m := New()
|
||||
if m == nil {
|
||||
t.Error("map is null.")
|
||||
}
|
||||
|
||||
if m.Count() != 0 {
|
||||
t.Error("new map should be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
m := New()
|
||||
elephant := Animal{"elephant"}
|
||||
monkey := Animal{"monkey"}
|
||||
|
||||
m.Set("elephant", elephant)
|
||||
m.Set("monkey", monkey)
|
||||
|
||||
if m.Count() != 2 {
|
||||
t.Error("map should contain exactly two elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertAbsent(t *testing.T) {
|
||||
m := New()
|
||||
elephant := Animal{"elephant"}
|
||||
monkey := Animal{"monkey"}
|
||||
|
||||
m.SetIfAbsent("elephant", elephant)
|
||||
if ok := m.SetIfAbsent("elephant", monkey); ok {
|
||||
t.Error("map set a new value even the entry is already present")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Get a missing element.
|
||||
val, ok := m.Get("Money")
|
||||
|
||||
if ok == true {
|
||||
t.Error("ok should be false when item is missing from map.")
|
||||
}
|
||||
|
||||
if val != nil {
|
||||
t.Error("Missing values should return as null.")
|
||||
}
|
||||
|
||||
elephant := Animal{"elephant"}
|
||||
m.Set("elephant", elephant)
|
||||
|
||||
// Retrieve inserted element.
|
||||
|
||||
tmp, ok := m.Get("elephant")
|
||||
elephant = tmp.(Animal) // Type assertion.
|
||||
|
||||
if ok == false {
|
||||
t.Error("ok should be true for item stored within the map.")
|
||||
}
|
||||
|
||||
if &elephant == nil {
|
||||
t.Error("expecting an element, not null.")
|
||||
}
|
||||
|
||||
if elephant.name != "elephant" {
|
||||
t.Error("item was modified.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHas(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Get a missing element.
|
||||
if m.Has("Money") == true {
|
||||
t.Error("element shouldn't exists")
|
||||
}
|
||||
|
||||
elephant := Animal{"elephant"}
|
||||
m.Set("elephant", elephant)
|
||||
|
||||
if m.Has("elephant") == false {
|
||||
t.Error("element exists, expecting Has to return True.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
monkey := Animal{"monkey"}
|
||||
m.Set("monkey", monkey)
|
||||
|
||||
m.Remove("monkey")
|
||||
|
||||
if m.Count() != 0 {
|
||||
t.Error("Expecting count to be zero once item was removed.")
|
||||
}
|
||||
|
||||
temp, ok := m.Get("monkey")
|
||||
|
||||
if ok != false {
|
||||
t.Error("Expecting ok to be false for missing items.")
|
||||
}
|
||||
|
||||
if temp != nil {
|
||||
t.Error("Expecting item to be nil after its removal.")
|
||||
}
|
||||
|
||||
// Remove a none existing element.
|
||||
m.Remove("noone")
|
||||
}
|
||||
|
||||
func TestPop(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
monkey := Animal{"monkey"}
|
||||
m.Set("monkey", monkey)
|
||||
|
||||
v, exists := m.Pop("monkey")
|
||||
|
||||
if !exists {
|
||||
t.Error("Pop didn't find a monkey.")
|
||||
}
|
||||
|
||||
m1, ok := v.(Animal)
|
||||
|
||||
if !ok || m1 != monkey {
|
||||
t.Error("Pop found something else, but monkey.")
|
||||
}
|
||||
|
||||
v2, exists2 := m.Pop("monkey")
|
||||
m1, ok = v2.(Animal)
|
||||
|
||||
if exists2 || ok || m1 == monkey {
|
||||
t.Error("Pop keeps finding monkey")
|
||||
}
|
||||
|
||||
if m.Count() != 0 {
|
||||
t.Error("Expecting count to be zero once item was Pop'ed.")
|
||||
}
|
||||
|
||||
temp, ok := m.Get("monkey")
|
||||
|
||||
if ok != false {
|
||||
t.Error("Expecting ok to be false for missing items.")
|
||||
}
|
||||
|
||||
if temp != nil {
|
||||
t.Error("Expecting item to be nil after its removal.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCount(t *testing.T) {
|
||||
m := New()
|
||||
for i := 0; i < 100; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
if m.Count() != 100 {
|
||||
t.Error("Expecting 100 element within map.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
if m.IsEmpty() == false {
|
||||
t.Error("new map should be empty")
|
||||
}
|
||||
|
||||
m.Set("elephant", Animal{"elephant"})
|
||||
|
||||
if m.IsEmpty() != false {
|
||||
t.Error("map shouldn't be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 100; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
counter := 0
|
||||
// Iterate over elements.
|
||||
for item := range m.Iter() {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
if counter != 100 {
|
||||
t.Error("We should have counted 100 elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedIterator(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 100; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
counter := 0
|
||||
// Iterate over elements.
|
||||
for item := range m.IterBuffered() {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
if counter != 100 {
|
||||
t.Error("We should have counted 100 elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterCb(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 100; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
counter := 0
|
||||
// Iterate over elements.
|
||||
m.IterCb(func(key string, v interface{}) {
|
||||
_, ok := v.(Animal)
|
||||
if !ok {
|
||||
t.Error("Expecting an animal object")
|
||||
}
|
||||
|
||||
counter++
|
||||
})
|
||||
if counter != 100 {
|
||||
t.Error("We should have counted 100 elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestItems(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 100; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
items := m.Items()
|
||||
|
||||
if len(items) != 100 {
|
||||
t.Error("We should have counted 100 elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrent(t *testing.T) {
|
||||
m := New()
|
||||
ch := make(chan int)
|
||||
const iterations = 1000
|
||||
var a [iterations]int
|
||||
|
||||
// Using go routines insert 1000 ints into our map.
|
||||
go func() {
|
||||
for i := 0; i < iterations/2; i++ {
|
||||
// Add item to map.
|
||||
m.Set(strconv.Itoa(i), i)
|
||||
|
||||
// Retrieve item from map.
|
||||
val, _ := m.Get(strconv.Itoa(i))
|
||||
|
||||
// Write to channel inserted value.
|
||||
ch <- val.(int)
|
||||
} // Call go routine with current index.
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for i := iterations / 2; i < iterations; i++ {
|
||||
// Add item to map.
|
||||
m.Set(strconv.Itoa(i), i)
|
||||
|
||||
// Retrieve item from map.
|
||||
val, _ := m.Get(strconv.Itoa(i))
|
||||
|
||||
// Write to channel inserted value.
|
||||
ch <- val.(int)
|
||||
} // Call go routine with current index.
|
||||
}()
|
||||
|
||||
// Wait for all go routines to finish.
|
||||
counter := 0
|
||||
for elem := range ch {
|
||||
a[counter] = elem
|
||||
counter++
|
||||
if counter == iterations {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts array, will make is simpler to verify all inserted values we're returned.
|
||||
sort.Ints(a[0:iterations])
|
||||
|
||||
// Make sure map contains 1000 elements.
|
||||
if m.Count() != iterations {
|
||||
t.Error("Expecting 1000 elements.")
|
||||
}
|
||||
|
||||
// Make sure all inserted values we're fetched from map.
|
||||
for i := 0; i < iterations; i++ {
|
||||
if i != a[i] {
|
||||
t.Error("missing value", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonMarshal(t *testing.T) {
|
||||
SHARD_COUNT = 2
|
||||
defer func() {
|
||||
SHARD_COUNT = 32
|
||||
}()
|
||||
expected := "{\"a\":1,\"b\":2}"
|
||||
m := New()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
j, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if string(j) != expected {
|
||||
t.Error("json", string(j), "differ from expected", expected)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
for i := 0; i < 100; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
keys := m.Keys()
|
||||
if len(keys) != 100 {
|
||||
t.Error("We should have counted 100 elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMInsert(t *testing.T) {
|
||||
animals := map[string]interface{}{
|
||||
"elephant": Animal{"elephant"},
|
||||
"monkey": Animal{"monkey"},
|
||||
}
|
||||
m := New()
|
||||
m.MSet(animals)
|
||||
|
||||
if m.Count() != 2 {
|
||||
t.Error("map should contain exactly two elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFnv32(t *testing.T) {
|
||||
key := []byte("ABC")
|
||||
|
||||
hasher := fnv.New32()
|
||||
hasher.Write(key)
|
||||
if fnv32(string(key)) != hasher.Sum32() {
|
||||
t.Errorf("Bundled fnv32 produced %d, expected result from hash/fnv32 is %d", fnv32(string(key)), hasher.Sum32())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsert(t *testing.T) {
|
||||
dolphin := Animal{"dolphin"}
|
||||
whale := Animal{"whale"}
|
||||
tiger := Animal{"tiger"}
|
||||
lion := Animal{"lion"}
|
||||
|
||||
cb := func(exists bool, valueInMap interface{}, newValue interface{}) interface{} {
|
||||
nv := newValue.(Animal)
|
||||
if !exists {
|
||||
return []Animal{nv}
|
||||
}
|
||||
res := valueInMap.([]Animal)
|
||||
return append(res, nv)
|
||||
}
|
||||
|
||||
m := New()
|
||||
m.Set("marine", []Animal{dolphin})
|
||||
m.Upsert("marine", whale, cb)
|
||||
m.Upsert("predator", tiger, cb)
|
||||
m.Upsert("predator", lion, cb)
|
||||
|
||||
if m.Count() != 2 {
|
||||
t.Error("map should contain exactly two elements.")
|
||||
}
|
||||
|
||||
compare := func(a, b []Animal) bool {
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
marineAnimals, ok := m.Get("marine")
|
||||
if !ok || !compare(marineAnimals.([]Animal), []Animal{dolphin, whale}) {
|
||||
t.Error("Set, then Upsert failed")
|
||||
}
|
||||
|
||||
predators, ok := m.Get("predator")
|
||||
if !ok || !compare(predators.([]Animal), []Animal{tiger, lion}) {
|
||||
t.Error("Upsert, then Upsert failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeysWhenRemoving(t *testing.T) {
|
||||
m := New()
|
||||
|
||||
// Insert 100 elements.
|
||||
Total := 100
|
||||
for i := 0; i < Total; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
// Remove 10 elements concurrently.
|
||||
Num := 10
|
||||
for i := 0; i < Num; i++ {
|
||||
go func(c *ConcurrentMap, n int) {
|
||||
c.Remove(strconv.Itoa(n))
|
||||
}(&m, i)
|
||||
}
|
||||
keys := m.Keys()
|
||||
for _, k := range keys {
|
||||
if k == "" {
|
||||
t.Error("Empty keys returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
func TestUnDrainedIter(t *testing.T) {
|
||||
m := New()
|
||||
// Insert 100 elements.
|
||||
Total := 100
|
||||
for i := 0; i < Total; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
counter := 0
|
||||
// Iterate over elements.
|
||||
ch := m.Iter()
|
||||
for item := range ch {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
if counter == 42 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := Total; i < 2*Total; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
for item := range ch {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
if counter != 100 {
|
||||
t.Error("We should have been right where we stopped")
|
||||
}
|
||||
|
||||
counter = 0
|
||||
for item := range m.IterBuffered() {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
if counter != 200 {
|
||||
t.Error("We should have counted 200 elements.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnDrainedIterBuffered(t *testing.T) {
|
||||
m := New()
|
||||
// Insert 100 elements.
|
||||
Total := 100
|
||||
for i := 0; i < Total; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
counter := 0
|
||||
// Iterate over elements.
|
||||
ch := m.IterBuffered()
|
||||
for item := range ch {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
if counter == 42 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := Total; i < 2*Total; i++ {
|
||||
m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)})
|
||||
}
|
||||
for item := range ch {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
if counter != 100 {
|
||||
t.Error("We should have been right where we stopped")
|
||||
}
|
||||
|
||||
counter = 0
|
||||
for item := range m.IterBuffered() {
|
||||
val := item.Val
|
||||
|
||||
if val == nil {
|
||||
t.Error("Expecting an object.")
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
if counter != 200 {
|
||||
t.Error("We should have counted 200 elements.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright 2014 Vic Demuzere
|
||||
//
|
||||
// Use of this source code is governed by the MIT license.
|
||||
|
||||
package ctcp
|
||||
|
||||
// Sources:
|
||||
// http://www.irchelp.org/irchelp/rfc/ctcpspec.html
|
||||
// http://www.kvirc.net/doc/doc_ctcp_handling.html
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gopkg.in/sorcix/irc.v2/internal"
|
||||
)
|
||||
|
||||
// Various constants used for formatting CTCP messages.
|
||||
const (
|
||||
delimiter byte = 0x01 // Prefix and suffix for CTCP tagged messages.
|
||||
space byte = 0x20 // Token separator
|
||||
|
||||
empty = "" // The empty string
|
||||
|
||||
timeFormat = time.RFC1123Z
|
||||
versionFormat = "Go v%s (" + runtime.GOOS + ", " + runtime.GOARCH + ")"
|
||||
)
|
||||
|
||||
// Tags extracted from the CTCP spec.
|
||||
const (
|
||||
ACTION = "ACTION"
|
||||
PING = "PING"
|
||||
PONG = "PONG"
|
||||
VERSION = "VERSION"
|
||||
USERINFO = "USERINFO"
|
||||
CLIENTINFO = "CLIENTINFO"
|
||||
FINGER = "FINGER"
|
||||
SOURCE = "SOURCE"
|
||||
TIME = "TIME"
|
||||
)
|
||||
|
||||
// Decode attempts to decode CTCP tagged data inside given message text.
|
||||
//
|
||||
// If the message text does not contain tagged data, ok will be false.
|
||||
//
|
||||
// <text> ::= <delim> <tag> [<SPACE> <message>] <delim>
|
||||
// <delim> ::= 0x01
|
||||
//
|
||||
func Decode(text string) (tag, message string, ok bool) {
|
||||
|
||||
// Fast path, return if this text does not contain a CTCP message.
|
||||
if len(text) < 3 || text[0] != delimiter || text[len(text)-1] != delimiter {
|
||||
return empty, empty, false
|
||||
}
|
||||
|
||||
s := internal.IndexByte(text, space)
|
||||
|
||||
if s < 0 {
|
||||
|
||||
// Messages may contain only a tag.
|
||||
return text[1 : len(text)-1], empty, true
|
||||
}
|
||||
|
||||
return text[1:s], text[s+1 : len(text)-1], true
|
||||
}
|
||||
|
||||
// Encode returns the IRC message text for CTCP tagged data.
|
||||
//
|
||||
// <text> ::= <delim> <tag> [<SPACE> <message>] <delim>
|
||||
// <delim> ::= 0x01
|
||||
//
|
||||
func Encode(tag, message string) (text string) {
|
||||
|
||||
switch {
|
||||
|
||||
// We can't build a valid CTCP tagged message without at least a tag.
|
||||
case len(tag) <= 0:
|
||||
return empty
|
||||
|
||||
// Tagged data with a message
|
||||
case len(message) > 0:
|
||||
return string(delimiter) + tag + string(space) + message + string(delimiter)
|
||||
|
||||
}
|
||||
|
||||
// Tagged data without a message
|
||||
return string(delimiter) + tag + string(delimiter)
|
||||
}
|
||||
|
||||
// Action is a shortcut for Encode(ctcp.ACTION, message).
|
||||
func Action(message string) string {
|
||||
return Encode(ACTION, message)
|
||||
}
|
||||
|
||||
// Ping is a shortcut for Encode(ctcp.PING, message).
|
||||
func Ping(message string) string {
|
||||
return Encode(PING, message)
|
||||
}
|
||||
|
||||
// Pong is a shortcut for Encode(ctcp.PONG, message).
|
||||
func Pong(message string) string {
|
||||
return Encode(PONG, message)
|
||||
}
|
||||
|
||||
// Version is a shortcut for Encode(ctcp.VERSION, message).
|
||||
func Version(message string) string {
|
||||
return Encode(VERSION, message)
|
||||
}
|
||||
|
||||
// VersionReply is a shortcut for ENCODE(ctcp.VERSION, go version info).
|
||||
func VersionReply() string {
|
||||
return Encode(VERSION, fmt.Sprintf(versionFormat, runtime.Version()))
|
||||
}
|
||||
|
||||
// UserInfo is a shortcut for Encode(ctcp.USERINFO, message).
|
||||
func UserInfo(message string) string {
|
||||
return Encode(USERINFO, message)
|
||||
}
|
||||
|
||||
// ClientInfo is a shortcut for Encode(ctcp.CLIENTINFO, message).
|
||||
func ClientInfo(message string) string {
|
||||
return Encode(CLIENTINFO, message)
|
||||
}
|
||||
|
||||
// Finger is a shortcut for Encode(ctcp.FINGER, message).
|
||||
func Finger(message string) string {
|
||||
return Encode(FINGER, message)
|
||||
}
|
||||
|
||||
// Source is a shortcut for Encode(ctcp.SOURCE, message).
|
||||
func Source(message string) string {
|
||||
return Encode(SOURCE, message)
|
||||
}
|
||||
|
||||
// Time is a shortcut for Encode(ctcp.TIME, message).
|
||||
func Time(message string) string {
|
||||
return Encode(TIME, message)
|
||||
}
|
||||
|
||||
// TimeReply is a shortcut for Encode(ctcp.TIME, currenttime).
|
||||
func TimeReply() string {
|
||||
return Encode(TIME, time.Now().Format(timeFormat))
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright 2014 Vic Demuzere
|
||||
//
|
||||
// Use of this source code is governed by the MIT license.
|
||||
|
||||
package ctcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
if _, _, ok := Decode("\x01\x01"); ok {
|
||||
t.Error("Message is invalid, but ok is true.")
|
||||
}
|
||||
if _, _, ok := Decode("\x01"); ok {
|
||||
t.Error("Message is invalid, but ok is true.")
|
||||
}
|
||||
if _, _, ok := Decode("\x01VERSION"); ok {
|
||||
t.Error("Message is invalid, but ok is true.")
|
||||
}
|
||||
if tag, message, ok := Decode("\x01VERSION\x01"); tag != "VERSION" || len(message) > 0 || !ok {
|
||||
t.Error("Message contains only a tag, wrong results.")
|
||||
}
|
||||
if tag, message, ok := Decode("\x01PING 123456789\x01"); tag != "PING" || message != "123456789" || !ok {
|
||||
t.Error("Message contains tag and a message, wrong results.")
|
||||
}
|
||||
if tag, message, ok := Decode("\x01CLIENTINFO A B C\x01"); tag != "CLIENTINFO" || message != "A B C" || !ok {
|
||||
t.Error("Message contains tag and a message with spaces, wrong results.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
if text := Encode("", "INVALID"); len(text) > 0 {
|
||||
t.Error("Message is invalid, but returns a non-empty string.")
|
||||
}
|
||||
if text := Encode("VERSION", ""); text != "\x01VERSION\x01" {
|
||||
t.Error("Message contains only a tag, wrong results.")
|
||||
}
|
||||
if text := Encode("PING", "123456789"); text != "\x01PING 123456789\x01" {
|
||||
t.Error("Message contains tag and a message, wrong results.")
|
||||
}
|
||||
if text := Encode("CLIENTINFO", "A B C"); text != "\x01CLIENTINFO A B C\x01" {
|
||||
t.Error("Message contains tag and a message with spaces, wrong results.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAction(t *testing.T) {
|
||||
if text := Action("A B C"); text != "\x01ACTION A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
if text := Ping("A B C"); text != "\x01PING A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPong(t *testing.T) {
|
||||
if text := Pong("A B C"); text != "\x01PONG A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
if text := Version("A B C"); text != "\x01VERSION A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserInfo(t *testing.T) {
|
||||
if text := UserInfo("A B C"); text != "\x01USERINFO A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientInfo(t *testing.T) {
|
||||
if text := ClientInfo("A B C"); text != "\x01CLIENTINFO A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinger(t *testing.T) {
|
||||
if text := Finger("A B C"); text != "\x01FINGER A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSource(t *testing.T) {
|
||||
if text := Source("A B C"); text != "\x01SOURCE A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
if text := Time("A B C"); text != "\x01TIME A B C\x01" {
|
||||
t.Error("Wrong result!")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2014 Vic Demuzere
|
||||
//
|
||||
// Use of this source code is governed by the MIT license.
|
||||
|
||||
// Package ctcp implements partial support for the Client-to-Client Protocol.
|
||||
//
|
||||
// CTCP defines extended messages using the standard PRIVMSG and NOTICE
|
||||
// commands in IRC. This means that any CTCP messages are embedded inside the
|
||||
// normal message text. Clients that don't support CTCP simply show
|
||||
// the encoded message to the user.
|
||||
//
|
||||
// Most IRC clients support only a subset of the protocol, and only a few
|
||||
// commands are actually used. This package aims to implement the most basic
|
||||
// CTCP messages: a single command per IRC message. Quoting is not supported.
|
||||
//
|
||||
// Example using the irc.Message type:
|
||||
//
|
||||
// m := irc.ParseMessage(...)
|
||||
//
|
||||
// if tag, text, ok := ctcp.Decode(m.Trailing); ok {
|
||||
// // This is a CTCP message.
|
||||
// } else {
|
||||
// // This is not a CTCP message.
|
||||
// }
|
||||
//
|
||||
// Similar, for encoding messages:
|
||||
//
|
||||
// m.Trailing = ctcp.Encode("ACTION","wants a cookie!")
|
||||
//
|
||||
// Do not send a complete IRC message to Decode, it won't work.
|
||||
package ctcp
|
|
@ -33,4 +33,4 @@
|
|||
// // Methods from both Encoder and Decoder are available
|
||||
// message, err := c.Decode()
|
||||
//
|
||||
package irc
|
||||
package irc // import "gopkg.in/sorcix/irc.v2"
|
||||
|
|
|
@ -0,0 +1,555 @@
|
|||
// Copyright 2014 Vic Demuzere
|
||||
//
|
||||
// Use of this source code is governed by the MIT license.
|
||||
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ExampleParseMessage() {
|
||||
message := ParseMessage("JOIN #help")
|
||||
|
||||
fmt.Println(message.Params[0])
|
||||
|
||||
// Output: #help
|
||||
}
|
||||
|
||||
func ExampleMessage_String() {
|
||||
message := &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "sorcix",
|
||||
User: "sorcix",
|
||||
Host: "myhostname",
|
||||
},
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"This is an example!"},
|
||||
}
|
||||
|
||||
fmt.Println(message.String())
|
||||
|
||||
// Output: :sorcix!sorcix@myhostname PRIVMSG :This is an example!
|
||||
}
|
||||
|
||||
var messageTests = [...]*struct {
|
||||
parsed *Message
|
||||
rawMessage string
|
||||
rawPrefix string
|
||||
hostmask bool // Is it very clear that the prefix is a hostname?
|
||||
server bool // Is the prefix a servername?
|
||||
}{
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "syrk",
|
||||
User: "kalt",
|
||||
Host: "millennium.stealth.net",
|
||||
},
|
||||
Command: "QUIT",
|
||||
Params: []string{"Gone to have lunch"},
|
||||
},
|
||||
rawMessage: ":syrk!kalt@millennium.stealth.net QUIT :Gone to have lunch",
|
||||
rawPrefix: "syrk!kalt@millennium.stealth.net",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "Trillian",
|
||||
},
|
||||
Command: "SQUIT",
|
||||
Params: []string{"cm22.eng.umd.edu", "Server out of control"},
|
||||
},
|
||||
rawMessage: ":Trillian SQUIT cm22.eng.umd.edu :Server out of control",
|
||||
rawPrefix: "Trillian",
|
||||
server: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "WiZ",
|
||||
User: "jto",
|
||||
Host: "tolsun.oulu.fi",
|
||||
},
|
||||
Command: "JOIN",
|
||||
Params: []string{"#Twilight_zone"},
|
||||
},
|
||||
rawMessage: ":WiZ!jto@tolsun.oulu.fi JOIN #Twilight_zone",
|
||||
rawPrefix: "WiZ!jto@tolsun.oulu.fi",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "WiZ",
|
||||
User: "jto",
|
||||
Host: "tolsun.oulu.fi",
|
||||
},
|
||||
Command: "PART",
|
||||
Params: []string{"#playzone", "I lost"},
|
||||
},
|
||||
rawMessage: ":WiZ!jto@tolsun.oulu.fi PART #playzone :I lost",
|
||||
rawPrefix: "WiZ!jto@tolsun.oulu.fi",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "WiZ",
|
||||
User: "jto",
|
||||
Host: "tolsun.oulu.fi",
|
||||
},
|
||||
Command: "MODE",
|
||||
Params: []string{"#eu-opers", "-l"},
|
||||
},
|
||||
rawMessage: ":WiZ!jto@tolsun.oulu.fi MODE #eu-opers -l",
|
||||
rawPrefix: "WiZ!jto@tolsun.oulu.fi",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Command: "MODE",
|
||||
Params: []string{"&oulu", "+b", "*!*@*.edu", "+e", "*!*@*.bu.edu"},
|
||||
},
|
||||
rawMessage: "MODE &oulu +b *!*@*.edu +e *!*@*.bu.edu",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"#channel", "Message with :colons!"},
|
||||
},
|
||||
rawMessage: "PRIVMSG #channel :Message with :colons!",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "irc.vives.lan",
|
||||
},
|
||||
Command: "251",
|
||||
Params: []string{"test", "There are 2 users and 0 services on 1 servers"},
|
||||
},
|
||||
rawMessage: ":irc.vives.lan 251 test :There are 2 users and 0 services on 1 servers",
|
||||
rawPrefix: "irc.vives.lan",
|
||||
server: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "irc.vives.lan",
|
||||
},
|
||||
Command: "376",
|
||||
Params: []string{"test", "End of MOTD command"},
|
||||
},
|
||||
rawMessage: ":irc.vives.lan 376 test :End of MOTD command",
|
||||
rawPrefix: "irc.vives.lan",
|
||||
server: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "irc.vives.lan",
|
||||
},
|
||||
Command: "250",
|
||||
Params: []string{"test", "Highest connection count: 1 (1 connections received)"},
|
||||
},
|
||||
rawMessage: ":irc.vives.lan 250 test :Highest connection count: 1 (1 connections received)",
|
||||
rawPrefix: "irc.vives.lan",
|
||||
server: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "sorcix",
|
||||
User: "~sorcix",
|
||||
Host: "sorcix.users.quakenet.org",
|
||||
},
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"#viveslan", "\001ACTION is testing CTCP messages!\001"},
|
||||
},
|
||||
rawMessage: ":sorcix!~sorcix@sorcix.users.quakenet.org PRIVMSG #viveslan :\001ACTION is testing CTCP messages!\001",
|
||||
rawPrefix: "sorcix!~sorcix@sorcix.users.quakenet.org",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "sorcix",
|
||||
User: "~sorcix",
|
||||
Host: "sorcix.users.quakenet.org",
|
||||
},
|
||||
Command: "NOTICE",
|
||||
Params: []string{"midnightfox", "\001PONG 1234567890\001"},
|
||||
},
|
||||
rawMessage: ":sorcix!~sorcix@sorcix.users.quakenet.org NOTICE midnightfox :\001PONG 1234567890\001",
|
||||
rawPrefix: "sorcix!~sorcix@sorcix.users.quakenet.org",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "a",
|
||||
User: "b",
|
||||
Host: "c",
|
||||
},
|
||||
Command: "QUIT",
|
||||
},
|
||||
rawMessage: ":a!b@c QUIT",
|
||||
rawPrefix: "a!b@c",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "a",
|
||||
User: "b",
|
||||
},
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"message"},
|
||||
},
|
||||
rawMessage: ":a!b PRIVMSG message",
|
||||
rawPrefix: "a!b",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "a",
|
||||
Host: "c",
|
||||
},
|
||||
Command: "NOTICE",
|
||||
Params: []string{":::Hey!"},
|
||||
},
|
||||
rawMessage: ":a@c NOTICE ::::Hey!",
|
||||
rawPrefix: "a@c",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "nick",
|
||||
},
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"$@", "This message contains a\ttab!"},
|
||||
},
|
||||
rawMessage: ":nick PRIVMSG $@ :This message contains a\ttab!",
|
||||
rawPrefix: "nick",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Command: "TEST",
|
||||
Params: []string{"$@", "", "param", "Trailing"},
|
||||
},
|
||||
rawMessage: "TEST $@ param Trailing",
|
||||
},
|
||||
{
|
||||
rawMessage: ": PRIVMSG test :Invalid message with empty prefix.",
|
||||
rawPrefix: "",
|
||||
},
|
||||
{
|
||||
rawMessage: ": PRIVMSG test :Invalid message with space prefix",
|
||||
rawPrefix: " ",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Command: "TOPIC",
|
||||
Params: []string{"#foo", ""},
|
||||
},
|
||||
rawMessage: "TOPIC #foo :",
|
||||
rawPrefix: "",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Prefix: &Prefix{
|
||||
Name: "name",
|
||||
User: "user",
|
||||
Host: "example.org",
|
||||
},
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"#test", "Message with spaces at the end! "},
|
||||
},
|
||||
rawMessage: ":name!user@example.org PRIVMSG #test :Message with spaces at the end! ",
|
||||
rawPrefix: "name!user@example.org",
|
||||
hostmask: true,
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Command: "PASS",
|
||||
Params: []string{"oauth:token_goes_here"},
|
||||
},
|
||||
rawMessage: "PASS oauth:token_goes_here",
|
||||
rawPrefix: "",
|
||||
},
|
||||
{
|
||||
parsed: &Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"#some:channel", "http://example.com"},
|
||||
},
|
||||
rawMessage: "PRIVMSG #some:channel http://example.com",
|
||||
rawPrefix: "",
|
||||
},
|
||||
}
|
||||
|
||||
// -----
|
||||
// PREFIX
|
||||
// -----
|
||||
|
||||
func TestPrefix_IsHostmask(t *testing.T) {
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no prefix
|
||||
if test.parsed == nil || test.parsed.Prefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if test.hostmask && !test.parsed.Prefix.IsHostmask() {
|
||||
t.Errorf("Prefix %d should be recognized as a hostmask!", i)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefix_IsServer(t *testing.T) {
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no prefix
|
||||
if test.parsed == nil || test.parsed.Prefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if test.server && !test.parsed.Prefix.IsServer() {
|
||||
t.Errorf("Prefix %d should be recognized as a server!", i)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefix_String(t *testing.T) {
|
||||
var s string
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no prefix
|
||||
if test.parsed == nil || test.parsed.Prefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert the prefix
|
||||
s = test.parsed.Prefix.String()
|
||||
|
||||
// Result should be the same as the value in rawMessage.
|
||||
if s != test.rawPrefix {
|
||||
t.Errorf("Failed to stringify prefix %d:", i)
|
||||
t.Logf("Output: %s", s)
|
||||
t.Logf("Expected: %s", test.rawPrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefix_Len(t *testing.T) {
|
||||
var l int
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no prefix
|
||||
if test.parsed == nil || test.parsed.Prefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
l = test.parsed.Prefix.Len()
|
||||
|
||||
// Result should be the same as the value in rawMessage.
|
||||
if l != len(test.rawPrefix) {
|
||||
t.Errorf("Failed to calculate prefix length %d:", i)
|
||||
t.Logf("Output: %d", l)
|
||||
t.Logf("Expected: %d", len(test.rawPrefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePrefix(t *testing.T) {
|
||||
var p *Prefix
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no prefix
|
||||
if test.parsed == nil || test.parsed.Prefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse the prefix
|
||||
p = ParsePrefix(test.rawPrefix)
|
||||
|
||||
// Result struct should be the same as the value in parsed.
|
||||
if *p != *test.parsed.Prefix {
|
||||
t.Errorf("Failed to parse prefix %d:", i)
|
||||
t.Logf("Output: %#v", p)
|
||||
t.Logf("Expected: %#v", test.parsed.Prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----
|
||||
// MESSAGE
|
||||
// -----
|
||||
|
||||
func TestMessage_String(t *testing.T) {
|
||||
var s string
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no valid struct
|
||||
if test.parsed == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert the prefix
|
||||
s = test.parsed.String()
|
||||
|
||||
// Result should be the same as the value in rawMessage.
|
||||
if s != test.rawMessage {
|
||||
t.Errorf("Failed to stringify message %d:", i)
|
||||
t.Logf("Output: %s", s)
|
||||
t.Logf("Expected: %s", test.rawMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessage_Len(t *testing.T) {
|
||||
var l int
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip tests that have no valid struct
|
||||
if test.parsed == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
l = test.parsed.Len()
|
||||
|
||||
// Result should be the same as the value in rawMessage.
|
||||
if l != len(test.rawMessage) {
|
||||
t.Errorf("Failed to calculate message length %d:", i)
|
||||
t.Logf("Output: %d", l)
|
||||
t.Logf("Expected: %d", len(test.rawMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMessage(t *testing.T) {
|
||||
var p *Message
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Parse the prefix
|
||||
p = ParseMessage(test.rawMessage)
|
||||
|
||||
// Result struct should be the same as the value in parsed.
|
||||
if !reflect.DeepEqual(p, test.parsed) {
|
||||
t.Errorf("Failed to parse message %d:", i)
|
||||
t.Logf("Output: %#v", p)
|
||||
t.Logf("Expected: %#v", test.parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----
|
||||
// MESSAGE DECODE -> ENCODE
|
||||
// -----
|
||||
|
||||
func TestMessageDecodeEncode(t *testing.T) {
|
||||
var (
|
||||
p *Message
|
||||
s string
|
||||
)
|
||||
|
||||
for i, test := range messageTests {
|
||||
|
||||
// Skip invalid messages
|
||||
if test.parsed == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message, then encode it again.
|
||||
p = ParseMessage(test.rawMessage)
|
||||
s = p.String()
|
||||
|
||||
// Result struct should be the same as the original.
|
||||
if s != test.rawMessage {
|
||||
t.Errorf("Message %d failed decode-encode sequence!", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----
|
||||
// BENCHMARK
|
||||
// -----
|
||||
|
||||
func BenchmarkPrefix_String_short(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
prefix := new(Prefix)
|
||||
prefix.Name = "Namename"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
prefix.String()
|
||||
}
|
||||
}
|
||||
func BenchmarkPrefix_String_long(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
prefix := new(Prefix)
|
||||
prefix.Name = "Namename"
|
||||
prefix.User = "Username"
|
||||
prefix.Host = "Hostname"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
prefix.String()
|
||||
}
|
||||
}
|
||||
func BenchmarkParsePrefix_short(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParsePrefix("Namename")
|
||||
}
|
||||
}
|
||||
func BenchmarkParsePrefix_long(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParsePrefix("Namename!Username@Hostname")
|
||||
}
|
||||
}
|
||||
func BenchmarkMessage_String(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
messageTests[0].parsed.String()
|
||||
}
|
||||
}
|
||||
func BenchmarkParseMessage_short(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseMessage("COMMAND arg1 :Message\r\n")
|
||||
}
|
||||
}
|
||||
func BenchmarkParseMessage_medium(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseMessage(":Namename COMMAND arg6 arg7 :Message message message\r\n")
|
||||
}
|
||||
}
|
||||
func BenchmarkParseMessage_long(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseMessage(":Namename!username@hostname COMMAND arg1 arg2 arg3 arg4 arg5 arg6 arg7 :Message message message message message\r\n")
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"crypto/tls"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -52,6 +53,18 @@ func Dial(addr string) (*Conn, error) {
|
|||
return NewConn(c), nil
|
||||
}
|
||||
|
||||
// DialTLS connects to the given address using tls.Dial and
|
||||
// then returns a new Conn for the connection.
|
||||
func DialTLS(addr string, config *tls.Config) (*Conn, error) {
|
||||
c, err := tls.Dial("tcp", addr, config)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(c), nil
|
||||
}
|
||||
|
||||
// Close closes the underlying ReadWriteCloser.
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2014 Vic Demuzere
|
||||
//
|
||||
// Use of this source code is governed by the MIT license.
|
||||
|
||||
package irc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// We use the Dial function as a simple shortcut for connecting to an IRC server using a standard TCP socket.
|
||||
func ExampleDial() {
|
||||
conn, err := Dial("irc.quakenet.org:6667")
|
||||
if err != nil {
|
||||
log.Fatalln("Could not connect to IRC server")
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// Use NewConn when you want to connect using something else than a standard TCP socket.
|
||||
// This example first opens an encrypted TLS connection and then uses that to communicate with the server.
|
||||
func ExampleNewConn() {
|
||||
tconn, err := tls.Dial("tcp", "irc.quakenet.org:6667", &tls.Config{})
|
||||
if err != nil {
|
||||
log.Fatalln("Could not connect to IRC server")
|
||||
}
|
||||
conn := NewConn(tconn)
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
var stream = "PING port80a.se.quakenet.org\r\n:port80a.se.quakenet.org PONG port80a.se.quakenet.org port80a.se.quakenet.org\r\nPING chat.freenode.net\r\n:wilhelm.freenode.net PONG wilhelm.freenode.net chat.freenode.net\r\n"
|
||||
|
||||
var result = [...]*Message{
|
||||
{
|
||||
Command: PING,
|
||||
Params: []string{"port80a.se.quakenet.org"},
|
||||
},
|
||||
{
|
||||
Prefix: &Prefix{
|
||||
Name: "port80a.se.quakenet.org",
|
||||
},
|
||||
Command: PONG,
|
||||
Params: []string{"port80a.se.quakenet.org", "port80a.se.quakenet.org"},
|
||||
},
|
||||
{
|
||||
Command: PING,
|
||||
Params: []string{"chat.freenode.net"},
|
||||
},
|
||||
{
|
||||
Prefix: &Prefix{
|
||||
Name: "wilhelm.freenode.net",
|
||||
},
|
||||
Command: PONG,
|
||||
Params: []string{"wilhelm.freenode.net", "chat.freenode.net"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDecoder_Decode(t *testing.T) {
|
||||
|
||||
reader := strings.NewReader(stream)
|
||||
dec := NewDecoder(reader)
|
||||
|
||||
for i, test := range result {
|
||||
if message, err := dec.Decode(); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err.Error())
|
||||
} else {
|
||||
if !reflect.DeepEqual(message, test) {
|
||||
t.Fatalf("Decoded message looks wrong! (%d)", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := dec.Decode(); err != io.EOF {
|
||||
t.Fatal("Decode should return an EOF error!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_Encode(t *testing.T) {
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
enc := NewEncoder(buffer)
|
||||
|
||||
for _, test := range result {
|
||||
if err := enc.Encode(test); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if buffer.String() != stream {
|
||||
t.Fatalf("Encoded stream looks wrong!")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue