Commit 74857fb8 authored by Zachary Seguin's avatar Zachary Seguin

Initial commit

parents
Pipeline #104 passed with stages
in 33 seconds
.vscode/
trainsim
image: golang:1.9
stages:
- build
- test
before_script:
- go get github.com/tools/godep
- mkdir -pv /go/src/git.zacharyseguin.ca/${CI_PROJECT_NAMESPACE}
- ln -sv ${CI_PROJECT_DIR} /go/src/git.zacharyseguin.ca/${CI_PROJECT_PATH}
- cd /go/src/git.zacharyseguin.ca/${CI_PROJECT_PATH}
go build:
stage: build
script:
- godep restore
- godep go build
artifacts:
expire_in: 1 week
paths:
- trainsim
go test:
stage: test
script:
- godep restore
- godep go test -v -cover -race ./...
coverage: '/^coverage: (\d+\.\d+)% of statements/'
\ No newline at end of file
{
"ImportPath": "git.zacharyseguin.ca/trainsim/trainsim",
"GoVersion": "go1.9",
"GodepVersion": "v79",
"Deps": [
{
"ImportPath": "github.com/nats-io/go-nats",
"Comment": "v1.3.0-21-gf0d9c59",
"Rev": "f0d9c5988d4c2a17ad466fcdffe010165c46434e"
},
{
"ImportPath": "github.com/nats-io/go-nats/encoders/builtin",
"Comment": "v1.3.0-21-gf0d9c59",
"Rev": "f0d9c5988d4c2a17ad466fcdffe010165c46434e"
},
{
"ImportPath": "github.com/nats-io/go-nats/util",
"Comment": "v1.3.0-21-gf0d9c59",
"Rev": "f0d9c5988d4c2a17ad466fcdffe010165c46434e"
},
{
"ImportPath": "github.com/nats-io/nuid",
"Comment": "v1.0.0-3-g33c6031",
"Rev": "33c603157d6fd1b0ac2599bcc4a286b36479a06d"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "287cf08546ab5e7e37d55a84f7ed3fd1db036de5"
}
]
}
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.
[![pipeline status](https://git.zacharyseguin.ca/trainsim/trainsim/badges/master/pipeline.svg)](https://git.zacharyseguin.ca/trainsim/trainsim/commits/master)
[![coverage report](https://git.zacharyseguin.ca/trainsim/trainsim/badges/master/coverage.svg)](https://git.zacharyseguin.ca/trainsim/trainsim/commits/master)
Goal is to simulate a CBTC system.
segments:
1:
type: straight
length: 1000
connections:
forward:
destination: 2
cost: 0
2:
type: straight
length: 1000
connections:
forward:
destination: 3
cost: 0
reverse:
destination: 1
cost: 0
3:
type: straight
length: 1000
connections:
reverse:
destination: 2
cost: 0
\ No newline at end of file
package main
import (
"io/ioutil"
"log"
"git.zacharyseguin.ca/trainsim/trainsim/track"
"gopkg.in/yaml.v2"
)
func main() {
trackYaml, err := ioutil.ReadFile("config/track.yml")
if err != nil {
log.Fatal(err)
}
var track track.Track
err = yaml.Unmarshal(trackYaml, &track)
if err != nil {
log.Fatal(err)
}
err = track.Validate()
if err != nil {
log.Fatal(err)
}
log.Println(track)
}
package track
// Segment of track.
type Segment struct {
Type SegmentType `yaml:"type" json:"type"`
// Length in metres.
Length int `yaml:"length" json:"length"`
// Connected segments. (map: [direction]id)
// TODO: Use a custom SegmentConnectionDirection type
Connections map[string]SegmentConnection `yaml:"connections" json:"connections"`
}
// SegmentConnection details a connection between two segments.
type SegmentConnection struct {
Destination string `yaml:"destination" json:"destination"`
Cost int `yaml:"cost" json:"cost"`
}
package track
import (
"errors"
"strings"
)
type SegmentType int
const (
// Straight segment of track.
Straight SegmentType = iota
// Unknown segment type.
Unknown
)
func (st SegmentType) String() string {
switch st {
case Straight:
return "Straight"
}
return "Unknown"
}
// UnmarshalYAML is a custom unmarshaller for SegmentType from yaml.
func (st *SegmentType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var rawType string
if err := unmarshal(&rawType); err != nil {
return err
}
t, err := ParseSegmentType(rawType)
*st = t
return err
}
// ParseSegmentType converts a string to a SegmentType.
func ParseSegmentType(s string) (SegmentType, error) {
s = strings.ToLower(s)
switch s {
case "straight":
return Straight, nil
}
return Unknown, errors.New("Invalid segment type provided")
}
package track
import (
"testing"
)
func TestParseSegmentStraightLowercase(t *testing.T) {
want := Straight
st, err := ParseSegmentType("straight")
if err != nil {
t.Fatal(err)
}
if st != want {
t.Errorf("Result was incorrect, got: %q, want: %q.", st, want)
}
}
func TestParseSegmentStraightUppercase(t *testing.T) {
want := Straight
st, err := ParseSegmentType("STRAIGHT")
if err != nil {
t.Fatal(err)
}
if st != want {
t.Errorf("Result was incorrect, got: %q, want: %q.", st, want)
}
}
func TestParseSegmentStraightMixedCase(t *testing.T) {
want := Straight
st, err := ParseSegmentType("sTRaigHt")
if err != nil {
t.Fatal(err)
}
if st != want {
t.Errorf("Result was incorrect, got: %q, want: %q.", st, want)
}
}
func TestSegmentStraightToString(t *testing.T) {
want := "Straight"
st := Straight
str := st.String()
if str != want {
t.Errorf("String() was incorrect, got: %q, want: %q.", str, want)
}
}
package track
import (
"fmt"
)
// Track details the track.
type Track struct {
Segments map[string]Segment `yaml:"segments" json:"segments"`
}
// Validate that the track is valid.
func (t *Track) Validate() error {
for id, segment := range t.Segments {
if segment.Length < 0 {
return fmt.Errorf("segment %q has invalid length %d", id, segment.Length)
}
for direction, connection := range segment.Connections {
if _, ok := t.Segments[connection.Destination]; !ok {
return fmt.Errorf("segment %q has invalid %q connection - destination %q not found", id, direction, connection.Destination)
}
if connection.Cost < 0 {
return fmt.Errorf("segment %q has invalid %q connection - invalid cost %d", id, direction, connection.Cost)
}
}
}
return nil
}
package track
import (
"testing"
"gopkg.in/yaml.v2"
)
func TestValidTrack(t *testing.T) {
str := `segments:
1:
type: straight
length: 1000
connections:
forward:
destination: 2
cost: 0
2:
type: straight
length: 1000
connections:
forward:
destination: 3
cost: 0
reverse:
destination: 1
cost: 0
3:
type: straight
length: 1000
connections:
reverse:
destination: 2
cost: 0
`
var track Track
err := yaml.Unmarshal([]byte(str), &track)
if err != nil {
t.Fatal(err)
}
err = track.Validate()
if err != nil {
t.Errorf("Track was not marked as valid, when it should have. Error was: %q", err)
}
}
func TestTrackWithMissingSegmentConnection(t *testing.T) {
str := `segments:
1:
type: straight
length: 1000
connections:
forward:
destination: 2
cost: 0
2:
type: straight
length: 1000
connections:
forward:
destination: 3
cost: 0
reverse:
destination: 1
cost: 0
3:
type: straight
length: 1000
connections:
forward:
destination: 4
cost: 0
reverse:
destination: 2
cost: 0
`
var track Track
err := yaml.Unmarshal([]byte(str), &track)
if err != nil {
t.Fatal(err)
}
err = track.Validate()
if err == nil {
t.Errorf("Track returned valid, when it should not have.")
}
}
func TestTrackWithInvalidSegmentLength(t *testing.T) {
str := `segments:
1:
type: straight
length: -100
2:
type: straight
length: 1000
connections:
reverse:
destination: 1
cost: 0
`
var track Track
err := yaml.Unmarshal([]byte(str), &track)
if err != nil {
t.Fatal(err)
}
err = track.Validate()
if err == nil {
t.Errorf("Track returned valid, when it should not have.")
}
}
func TestTrackWithInvalidConnectionWeight(t *testing.T) {
str := `segments:
1:
type: straight
length: -100
connections:
forward:
destination: 2
cost: -10
2:
type: straight
length: 1000
connections:
reverse:
destination: 1
cost: 0
`
var track Track
err := yaml.Unmarshal([]byte(str), &track)
if err != nil {
t.Fatal(err)
}
err = track.Validate()
if err == nil {
t.Errorf("Track returned valid, when it should not have.")
}
}
func TestTrackWithZeroSegmentLength(t *testing.T) {
str := `segments:
1:
type: straight
length: 0
`
var track Track
err := yaml.Unmarshal([]byte(str), &track)
if err != nil {
t.Fatal(err)
}
err = track.Validate()
if err != nil {
t.Errorf("Track was not marked as valid, when it should have. Error was: %q", err)
}
}
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
# Emacs
*~
\#*\#
.\#*
# vi/vim
.??*.swp
# Mac
.DS_Store
# Eclipse
.project
.settings/
# bin
language: go
sudo: false
go:
- 1.9.x
- 1.8.x
- 1.7.x
install:
- go get -t ./...
- go get github.com/nats-io/gnatsd
- go get github.com/mattn/goveralls
- go get github.com/wadey/gocovmerge
- go get -u honnef.co/go/tools/cmd/megacheck
- go get -u github.com/client9/misspell/cmd/misspell
before_script:
- $(exit $(go fmt ./... | wc -l))
- go vet ./...
- misspell -error -locale US .
- megacheck -ignore "$(cat staticcheck.ignore)" ./...
script:
- go test -i -race ./...
- if [[ "$TRAVIS_GO_VERSION" == 1.7.* ]]; then ./scripts/cov.sh TRAVIS; else go test -v -race ./...; fi
The MIT License (MIT)
Copyright (c) 2012-2017 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This diff is collapsed.
- [ ] Better constructors, options handling
- [ ] Functions for callback settings after connection created.
- [ ] Better options for subscriptions. Slow Consumer state settable, Go routines vs Inline.
- [ ] Move off of channels for subscribers, use syncPool linkedLists, etc with highwater.
- [ ] Test for valid subjects on publish and subscribe?
- [ ] SyncSubscriber and Next for EncodedConn
- [ ] Fast Publisher?
- [ ] pooling for structs used? leaky bucket?
- [ ] Timeout 0 should work as no timeout
- [x] Ping timer
- [x] Name in Connect for gnatsd
- [x] Asynchronous error handling
- [x] Parser rewrite
- [x] Reconnect
- [x] Hide Lock
- [x] Easier encoder interface
- [x] QueueSubscribeSync
- [x] Make nats specific errors prefixed with 'nats:'
- [x] API test for closed connection
- [x] TLS/SSL
- [x] Stats collection
- [x] Disconnect detection
- [x] Optimized Publish (coalescing)
- [x] Do Examples via Go style
- [x] Standardized Errors
// Copyright 2012-2017 Apcera Inc. All rights reserved.
// +build go1.7
// A Go client for the NATS messaging system (https://nats.io).
package nats
import (
"context"
"fmt"
"reflect"
)
// RequestWithContext takes a context, a subject and payload
// in bytes and request expecting a single response.
func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
if ctx == nil {
return nil, ErrInvalidContext
}
if nc == nil {
return nil, ErrInvalidConnection
}
nc.mu.Lock()
// If user wants the old style.
if nc.Opts.UseOldRequestStyle {
nc.mu.Unlock()
return nc.oldRequestWithContext(ctx, subj, data)
}
// Do setup for the new style.
if nc.respMap == nil {
// _INBOX wildcard
nc.respSub = fmt.Sprintf("%s.*", NewInbox())
nc.respMap = make(map[string]chan *Msg)
}
// Create literal Inbox and map to a chan msg.
mch := make(chan *Msg, RequestChanLen)
respInbox := nc.newRespInbox()
token := respToken(respInbox)
nc.respMap[token] = mch
createSub := nc.respMux == nil
ginbox := nc.respSub
nc.mu.Unlock()
if createSub {
// Make sure scoped subscription is setup only once.
var err error
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
if err != nil {
return nil, err
}
}
err := nc.PublishRequest(subj, respInbox, data)
if err != nil {
return nil, err
}
var ok bool
var msg *Msg
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
}
case <-ctx.Done():
nc.mu.Lock()
delete(nc.respMap, token)