Skip to content
Snippets Groups Projects
Unverified Commit 2e0c0357 authored by Silas Davis's avatar Silas Davis
Browse files

Project: remove glide, tear down various project cruft and unnecessary dependency

packages, and dubious tests, relocate nearer to code being tested.
Rewording of some docs, use gopkg for deps in makefile.

Signed-off-by: default avatarSilas Davis <silas@monax.io>
parent 4462e819
No related branches found
No related tags found
No related merge requests found
Showing
with 71 additions and 1444 deletions
......@@ -20,8 +20,6 @@ jobs:
<<: *defaults
steps:
- checkout
- run: go get github.com/Masterminds/glide
- run: glide install
# Just persist the entire working dir (burrow checkout)
- persist_to_workspace:
......
......@@ -79,8 +79,8 @@ erase_vendor:
# install vendor uses glide to install vendored dependencies
.PHONY: install_vendor
install_vendor:
go get github.com/Masterminds/glide
glide install
@go get -u github.com/golang/dep/cmd/dep
@dep ensure -v
# Dumps Solidity interface contracts for SNatives
.PHONY: snatives
......
......@@ -29,13 +29,6 @@ Hyperledger Burrow is a permissioned blockchain node that executes smart contrac
- **Application Binary Interface (ABI):** transactions need to be formulated in a binary format that can be processed by the blockchain node. Currently tooling provides functionality to compile, deploy and link solidity smart contracts and formulate transactions to call smart contracts on the chain. For proof-of-concept purposes we provide a monax-contracts.js library that automatically mirrors the smart contracts deployed on the chain and to develop middleware solutions against the blockchain network. Future work on the light client will be aware of the ABI to natively translate calls on the API into signed transactions that can be broadcast on the network.
- **API Gateway:** Burrow exposes REST and JSON-RPC endpoints to interact with the blockchain network and the application state through broadcasting transactions, or querying the current state of the application. Websockets allow to subscribe to events, which is particularly valuable as the consensus engine and smart contract application can give unambiguously finalised results to transactions within one blocktime of about one second.
Burrow has been architected with a longer term vision on security and data privacy from the outset:
- **Cryptographically Secured Consensus:** proof-of-stake Tendermint protocol achieves consensus over a known set of validators where every block is closed with cryptographic signatures from a majority of validators only. No unknown variables come into play while reaching consensus on the network (as is the case for proof-of-work consensus). This guarantees that all actions on the network are fully cryptographically verified and traceable.
- **Remote Signing:** transactions can be signed by elliptic curve cryptographic algorithms, either ed25519/sha512 or secp256k1/sha256 are currently supported. Burrow connects to a remote signing solution to generate key pairs and request signatures. Monax-keys is a placeholder for a reverse proxy into your secure signing solution. This has always been the case for transaction formulation and work continues to enable remote signing for the validator block signatures too.
- **Secure Signing:** Monax is a legal engineering company; we partner with expert companies to natively support secure signing solutions going forward.
- **Multi-chain Universe (Step 1 of 3):** from the start the monax platform has been conceived for orchestrating many chains, as exemplified by the command “monax chains make” or by that transactions are only valid on the intended chain. Separating state into different chains is only the first of three steps towards privacy on smart contract chains (see future work below).
## Installation
`burrow` is intended to be used by the `monax chains` command via [monax](https://monax.io/docs). Available commands such as `make | start | stop | logs | inspect | update` are used for chain lifecycle management.
......
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package integral
func MaxInt8(a, b int8) int8 {
if a > b {
return a
}
return b
}
func MaxUint8(a, b uint8) uint8 {
if a > b {
return a
}
return b
}
func MaxInt16(a, b int16) int16 {
if a > b {
return a
}
return b
}
func MaxUint16(a, b uint16) uint16 {
if a > b {
return a
}
return b
}
func MaxInt32(a, b int32) int32 {
if a > b {
return a
}
return b
}
func MaxUint32(a, b uint32) uint32 {
if a > b {
return a
}
return b
}
func MaxInt64(a, b int64) int64 {
if a > b {
return a
}
return b
}
func MaxUint64(a, b uint64) uint64 {
if a > b {
return a
}
return b
}
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
func MaxUint(a, b uint) uint {
if a > b {
return a
}
return b
}
//-----------------------------------------------------------------------------
func MinInt8(a, b int8) int8 {
if a < b {
return a
}
return b
}
func MinUint8(a, b uint8) uint8 {
if a < b {
return a
}
return b
}
func MinInt16(a, b int16) int16 {
if a < b {
return a
}
return b
}
func MinUint16(a, b uint16) uint16 {
if a < b {
return a
}
return b
}
func MinInt32(a, b int32) int32 {
if a < b {
return a
}
return b
}
func MinUint32(a, b uint32) uint32 {
if a < b {
return a
}
return b
}
func MinInt64(a, b int64) int64 {
if a < b {
return a
}
return b
}
func MinUint64(a, b uint64) uint64 {
if a < b {
return a
}
return b
}
func MinInt(a, b int) int {
if a < b {
return a
}
return b
}
func MinUint(a, b uint) uint {
if a < b {
return a
}
return b
}
//-----------------------------------------------------------------------------
func ExpUint64(a, b uint64) uint64 {
accum := uint64(1)
for b > 0 {
if b&1 == 1 {
accum *= a
}
a *= a
b >>= 1
}
return accum
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package random
import (
crand "crypto/rand"
"math/rand"
"time"
"github.com/hyperledger/burrow/common/sanity"
)
const (
strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters
)
func init() {
b := cRandBytes(8)
var seed uint64
for i := 0; i < 8; i++ {
seed |= uint64(b[i])
seed <<= 8
}
rand.Seed(int64(seed))
}
// Constructs an alphanumeric string of given length.
func RandStr(length int) string {
chars := []byte{}
MAIN_LOOP:
for {
val := rand.Int63()
for i := 0; i < 10; i++ {
v := int(val & 0x3f) // rightmost 6 bits
if v >= 62 { // only 62 characters in strChars
val >>= 6
continue
} else {
chars = append(chars, strChars[v])
if len(chars) == length {
break MAIN_LOOP
}
val >>= 6
}
}
}
return string(chars)
}
func RandUint16() uint16 {
return uint16(rand.Uint32() & (1<<16 - 1))
}
func RandUint32() uint32 {
return rand.Uint32()
}
func RandUint64() uint64 {
return uint64(rand.Uint32())<<32 + uint64(rand.Uint32())
}
func RandUint() uint {
return uint(rand.Int())
}
func RandInt16() int16 {
return int16(rand.Uint32() & (1<<16 - 1))
}
func RandInt32() int32 {
return int32(rand.Uint32())
}
func RandInt64() int64 {
return int64(rand.Uint32())<<32 + int64(rand.Uint32())
}
func RandInt() int {
return rand.Int()
}
// Distributed pseudo-exponentially to test for various cases
func RandUint16Exp() uint16 {
bits := rand.Uint32() % 16
if bits == 0 {
return 0
}
n := uint16(1 << (bits - 1))
n += uint16(rand.Int31()) & ((1 << (bits - 1)) - 1)
return n
}
// Distributed pseudo-exponentially to test for various cases
func RandUint32Exp() uint32 {
bits := rand.Uint32() % 32
if bits == 0 {
return 0
}
n := uint32(1 << (bits - 1))
n += uint32(rand.Int31()) & ((1 << (bits - 1)) - 1)
return n
}
// Distributed pseudo-exponentially to test for various cases
func RandUint64Exp() uint64 {
bits := rand.Uint32() % 64
if bits == 0 {
return 0
}
n := uint64(1 << (bits - 1))
n += uint64(rand.Int63()) & ((1 << (bits - 1)) - 1)
return n
}
func RandFloat32() float32 {
return rand.Float32()
}
func RandTime() time.Time {
return time.Unix(int64(RandUint64Exp()), 0)
}
func RandBytes(n int) []byte {
bs := make([]byte, n)
for i := 0; i < n; i++ {
bs[i] = byte(rand.Intn(256))
}
return bs
}
// NOTE: This relies on the os's random number generator.
// For real security, we should salt that with some seed.
// See github.com/tendermint/go-crypto for a more secure reader.
func cRandBytes(numBytes int) []byte {
b := make([]byte, numBytes)
_, err := crand.Read(b)
if err != nil {
sanity.PanicCrisis(err)
}
return b
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sanity
import (
"fmt"
)
//--------------------------------------------------------------------------------------------------
// panic wrappers
// NOTE: [ben] Fail fast and fail hard, these are wrappers that point to code that needs
// to be addressed, but simplify finding them in the code;
// A panic resulting from a sanity check means there is a programmer error
// and some gaurantee is not satisfied.
func PanicSanity(v interface{}) {
panic(fmt.Sprintf("Paniced on a Sanity Check: %v", v))
}
// A panic here means something has gone horribly wrong, in the form of data corruption or
// failure of the operating system. In a correct/healthy system, these should never fire.
// If they do, it's indicative of a much more serious problem.
func PanicCrisis(v interface{}) {
panic(fmt.Sprintf("Paniced on a Crisis: %v", v))
}
// Indicates a failure of consensus. Someone was malicious or something has
// gone horribly wrong. These should really boot us into an "emergency-recover" mode
func PanicConsensus(v interface{}) {
panic(fmt.Sprintf("Paniced on a Consensus Failure: %v", v))
}
// For those times when we're not sure if we should panic
func PanicQ(v interface{}) {
panic(fmt.Sprintf("Paniced questionably: %v", v))
}
### Deployment
Included in this directory are some template files for running Burrow in a
cluster orchestration environment. [start_in_cluster](start_in_cluster)
is a general purpose script and the files in [kubernetes](kubernetes) are some
example Service and Deployment files that illustrates its possible usage.
#### start_in_cluster
[start_in_cluster](start_in_cluster) takes its parameters as environment variables.
You can find the variables used at the top of the file along with their defaults.
#### Kubernetes
[all_nodes.yml](kubernetes/all_nodes.yaml) is a Kubernetes Service definition
that launches an entire network of nodes based on Deployment definitions like the
example [node000-deploy.yaml](kubernetes/node000-deploy.yaml). Each validating
node should have it's own Deployment defintion like the one found in
[node000-deploy.yaml](kubernetes/node000-deploy.yaml)
# All Nodes - Load balanced API ports
kind: Service
apiVersion: v1
metadata:
name: your-app-chain-api
labels:
app: your-app
tier: chain
chain_name: your-chain
vendor: monax
spec:
sessionAffinity: ClientIP
selector:
app: your-app
tier: chain
# node_number: "001"
ports:
- protocol: TCP
port: 1337
targetPort: 1337
name: your-app-chain-api
# All Nodes - genesis.json as ConfigMap
---
kind: ConfigMap
apiVersion: v1
metadata:
name: your-app-ecosystem-chain-genesis
labels:
app: your-app-ecosystem
tier: chain
chain_name: your-app
vendor: monax
data:
chain-genesis: |
{"chain_id":"your-chain","accounts":[{"address":"BD1EA8ABA4B44094A406092922AF7CD92E01E5FE","amount":99999999999999,"name":"your-app_root_000","permissions":{"base":{"perms":16383,"set":16383},"roles":[]}},{"address":"E69B68990FECA85E06421859DBD4B2958C80A0D5","amount":9999999999,"name":"your-app_participant_000","permissions":{"base":{"perms":2118,"set":16383},"roles":[]}},{"address":"15416FC158C2D106B2994C82724B3DBBA47CDF79","amount":9999999999,"name":"your-app_participant_001","permissions":{"base":{"perms":2118,"set":16383},"roles":[]}},{"address":"EA6EBC0569495F98F159D533E8DD7D1D3DCFC80C","amount":9999999999,"name":"your-app_participant_002","permissions":{"base":{"perms":2118,"set":16383},"roles":[]}},{"address":"242075F27576B80F2A2805488E23203CBCBCBDFB","amount":99999999999999,"name":"your-app_validator_000","permissions":{"base":{"perms":16383,"set":16383},"roles":[]}},{"address":"8B84E223A42DEDC8C62A19F99C45C94B807BDFB6","amount":9999999999,"name":"your-app_validator_001","permissions":{"base":{"perms":32,"set":16383},"roles":[]}},{"address":"68AFC7ADB01A8CF8F9B93166D749F0067D250981","amount":9999999999,"name":"your-app_validator_002","permissions":{"base":{"perms":32,"set":16383},"roles":[]}},{"address":"443BAD24961BEE41F052C6B55AF58BDE9A4DB75F","amount":9999999999,"name":"your-app_validator_003","permissions":{"base":{"perms":32,"set":16383},"roles":[]}}],"validators":[{"pub_key":[1,"ED3EAEAAA735EC41A3625BB8AAC754A381A5726269E584B77A594E4197F2B516"],"name":"your-app_validator_000","amount":9999999998,"unbond_to":[{"address":"242075F27576B80F2A2805488E23203CBCBCBDFB","amount":9999999998}]},{"pub_key":[1,"F8F98DE0E65FBF8FBA5CE0813898F4E2FAFC34DD37FDB45B58D73B6D75DCB9AE"],"name":"your-app_validator_001","amount":9999999998,"unbond_to":[{"address":"8B84E223A42DEDC8C62A19F99C45C94B807BDFB6","amount":9999999998}]},{"pub_key":[1,"31D592F81F688AEE06B3124CBAC5AE3E04B5398A34325D4D32A25105B9588385"],"name":"your-app_validator_002","amount":9999999998,"unbond_to":[{"address":"68AFC7ADB01A8CF8F9B93166D749F0067D250981","amount":9999999998}]},{"pub_key":[1,"A1562215F9025DAA180B06C4DD9254D6B92C9F6C19219A359956941EB5924148"],"name":"your-app_validator_003","amount":9999999998,"unbond_to":[{"address":"443BAD24961BEE41F052C6B55AF58BDE9A4DB75F","amount":9999999998}]}]}
# Node 000 - Deployment
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: your-app-ecosystem-chain-000
spec:
replicas: 1
template:
metadata:
labels:
app: your-app-ecosystem
tier: chain
node_number: "000"
chain_name: your-app
vendor: monax
spec:
containers:
- name: your-app-ecosystem-chain-000
image: "your-app-ecosystem-chain:latest"
imagePullPolicy: IfNotPresent
env:
- name: CHAIN_NAME
value: your-chain
- name: CHAIN_NODE_NUMBER
value: "000"
- name: CHAIN_SEEDS
value: "your-app-chain-000:46656,your-app-chain-001:46656,your-app-chain-002:46656,your-app-chain-003:46656"
- name: CHAIN_API_PORT
value: "1337"
- name: CHAIN_PEER_PORT
value: "46656"
- name: CHAIN_RPC_PORT
value: "46657"
- name: CHAIN_LOG_LEVEL
value: notice
- name: CHAIN_GENESIS
valueFrom:
configMapKeyRef:
name: your-app-ecosystem-chain-genesis
key: chain-genesis
- name: KEY_ADDRESS
valueFrom:
secretKeyRef:
name: your-app-ecosystem-chain-000-keys
key: address
- name: KEY_PUBLIC
valueFrom:
secretKeyRef:
name: your-app-ecosystem-chain-000-keys
key: public-key
- name: KEY_PRIVATE
valueFrom:
secretKeyRef:
name: your-app-ecosystem-chain-000-keys
key: private-key
- name: ORGANIZATION_NAME
value: allianz
ports:
- containerPort: 46656
- containerPort: 46657
- containerPort: 1337
restartPolicy: Always
#!/usr/bin/env bash
set -e
# This script is a wrapper for running burrow in a cluster, that is by a cluster
# orchestration environment like Kubernetes or Mesos
# For why this is necessary see ->
# https://github.com/kubernetes/kubernetes/issues/23404
CHAIN_NAME=$(echo $CHAIN_NAME | tr -d '\n')
CHAIN_NODE_NUMBER=$(echo $CHAIN_NODE_NUMBER | tr -d '\n')
CHAIN_SEEDS=$(echo $CHAIN_SEEDS | tr -d '\n')
MONAX_PATH=${MONAX_PATH:-$HOME/.monax}
MONAX_PATH=$(echo $MONAX_PATH | tr -d '\n')
KEY_ADDRESS=$(echo $KEY_ADDRESS | tr -d '\n')
KEY_PUBLIC=$(echo $KEY_PUBLIC | tr -d '\n')
KEY_PRIVATE=$(echo $KEY_PRIVATE | tr -d '\n')
ORG_NAME=$(echo $ORG_NAME | tr -d '\n')
# Normal var setting
CHAIN_DIR=$MONAX_PATH/chains/$CHAIN_NAME
CHAIN_ID=${CHAIN_ID:-$CHAIN_NAME}
CHAIN_API_PORT=${CHAIN_API_PORT:-1337}
CHAIN_PEER_PORT=${CHAIN_PEER_PORT:-46656}
CHAIN_RPC_PORT=${CHAIN_RPC_PORT:-46657}
CHAIN_NODE_NUMBER=${CHAIN_NODE_NUMBER:-"000"}
CHAIN_SEEDS=${CHAIN_SEEDS:-""}
ORG_NAME=${ORG_NAME:-"myGreatOrg"}
# All known variables should either have defaults set above, or checks here.
check() {
echo -e "\tChecking address of key."
[ -z "$KEY_ADDRESS" ] && { echo "Sad marmot face. Please set KEY_ADDRESS and re-run me."; exit 1; }
echo -e "\tChecking public key."
[ -z "$KEY_PUBLIC" ] && { echo "Sad marmot face. Please set KEY_PUBLIC and re-run me."; exit 1; }
echo -e "\tChecking private key."
[ -z "$KEY_PRIVATE" ] && { echo "Sad marmot face. Please set KEY_PRIVATE and re-run me."; exit 1; }
echo -e "\tChecking chain name."
[ -z "$CHAIN_NAME" ] && { echo "Sad marmot face. Please set CHAIN_NAME and re-run me."; exit 1; }
echo -e "\tChecking genesis."
if [ -z "$CHAIN_GENESIS" ]
then
if [ ! -e "$CHAIN_DIR"/genesis.json ]
then
echo "Sad marmot face. Please set CHAIN_GENESIS and re-run me."
exit 1
fi
fi
echo -e "\tChecks complete."
}
setup_dir() {
if [ ! -d "$CHAIN_DIR" ]
then
echo -e "\tChain dir does not exist. Creating."
mkdir -p $CHAIN_DIR
else
echo -e "\tChain dir exists. Not creating."
fi
cd $CHAIN_DIR
echo -e "\tChain dir setup."
}
write_config() {
cat <<EOF > config.toml
[chain]
assert_chain_id = "$CHAIN_ID"
major_version = 0
minor_version = 12
genesis_file = "genesis.json"
[chain.consensus]
name = "tendermint"
major_version = 0
minor_version = 6
relative_root = "tendermint"
[chain.manager]
name = "burrowmint"
major_version = 0
minor_version = 12
relative_root = "burrowmint"
[servers]
[servers.bind]
address = ""
port = $CHAIN_API_PORT
[servers.tls]
tls = false
cert_path = ""
key_path = ""
[servers.cors]
enable = false
allow_origins = []
allow_credentials = false
allow_methods = []
allow_headers = []
expose_headers = []
max_age = 0
[servers.http]
json_rpc_endpoint = "/rpc"
[servers.websocket]
endpoint = "/socketrpc"
max_sessions = 50
read_buffer_size = 4096
write_buffer_size = 4096
[servers.tendermint]
rpc_local_address = "0.0.0.0:$CHAIN_RPC_PORT"
endpoint = "/websocket"
[tendermint]
private_validator_file = "priv_validator.json"
[tendermint.configuration]
moniker = "$CHAIN_NAME-$ORG_NAME-validator-$CHAIN_NODE_NUMBER"
seeds = "$CHAIN_SEEDS"
fast_sync = false
db_backend = "leveldb"
log_level = "$CHAIN_LOG_LEVEL"
node_laddr = "0.0.0.0:$CHAIN_PEER_PORT"
rpc_laddr = "0.0.0.0:$CHAIN_RPC_PORT"
proxy_app = "tcp://127.0.0.1:46658"
[tendermint.configuration.p2p]
dial_timeout_seconds = 3
handshake_timeout_seconds = 20
max_num_peers = 20
authenticated_encryption = true
send_rate = 512000
recv_rate = 512000
fuzz_enable = false # use the fuzz wrapped conn
fuzz_active = false # toggle fuzzing
fuzz_mode = "drop" # eg. drop, delay
fuzz_max_delay_milliseconds = 3000
fuzz_prob_drop_rw = 0.2
fuzz_prob_drop_conn = 0.00
fuzz_prob_sleep = 0.00
[burrowmint]
db_backend = "leveldb"
tendermint_host = "0.0.0.0:$CHAIN_RPC_PORT"
EOF
echo -e "\tConfig file written."
}
write_key_file() {
cat <<EOF > priv_validator.json
{
"address": "$KEY_ADDRESS",
"pub_key": [
1,
"$KEY_PUBLIC"
],
"priv_key": [
1,
"$KEY_PRIVATE"
],
"last_height": 0,
"last_round": 0,
"last_step": 0
}
EOF
echo -e "\tKey file written."
}
write_genesis_file() {
[ -z "$CHAIN_GENESIS" ] && echo -e "\tUsing preloaded genesis file." && return 0
echo -e "\tWriting genesis file from environment variables."
echo $CHAIN_GENESIS > genesis.json
}
main() {
echo "Running pre-boot checks."
check
echo "Setting up chain directory."
setup_dir
echo "Writing config file."
write_config
echo "Writing key file."
write_key_file
echo "Writing genesis file."
write_genesis_file
sleep 2 # hack to let the cluster provision echo "Starting burrow"
burrow serve
}
main $@
\ No newline at end of file
......@@ -211,16 +211,6 @@ These are the types of transactions. Note that in DApp programming you would onl
}
```
#### DupeoutTx
```
{
address: <string>
vote_a: <Vote>
vote_b: <Vote>
}
```
These are the support types that are referenced in the transactions:
#### TxInput
......@@ -434,18 +424,6 @@ Event object:
<Tx>
```
#### Dupeout
This notifies you when a dupeout event happens.
Event ID: `Dupeout`
Event object:
```
<Tx>
```
<a name="namereg">
### Name-registry
......
......@@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filters
package event
import (
"fmt"
"sync"
"testing"
event "github.com/hyperledger/burrow/event"
"github.com/stretchr/testify/suite"
)
......@@ -38,12 +37,12 @@ type IntegerFilter struct {
match func(int64, int64) bool
}
func (this *IntegerFilter) Configure(fd *event.FilterData) error {
val, err := event.ParseNumberValue(fd.Value)
func (this *IntegerFilter) Configure(fd *FilterData) error {
val, err := ParseNumberValue(fd.Value)
if err != nil {
return err
}
match, err2 := event.GetRangeFilter(fd.Op, "integer")
match, err2 := GetRangeFilter(fd.Op, "integer")
if err2 != nil {
return err2
}
......@@ -69,8 +68,8 @@ type StringFilter struct {
match func(string, string) bool
}
func (this *StringFilter) Configure(fd *event.FilterData) error {
match, err := event.GetStringFilter(fd.Op, "string")
func (this *StringFilter) Configure(fd *FilterData) error {
match, err := GetStringFilter(fd.Op, "string")
if err != nil {
return err
}
......@@ -92,17 +91,17 @@ func (this *StringFilter) Match(v interface{}) bool {
type FilterSuite struct {
suite.Suite
objects []FilterableObject
filterFactory *event.FilterFactory
filterFactory *FilterFactory
}
func (this *FilterSuite) SetupSuite() {
func (fs *FilterSuite) SetupSuite() {
objects := make([]FilterableObject, OBJECTS)
for i := 0; i < 100; i++ {
objects[i] = FilterableObject{i, fmt.Sprintf("string%d", i)}
}
ff := event.NewFilterFactory()
ff := NewFilterFactory()
ff.RegisterFilterPool("integer", &sync.Pool{
New: func() interface{} {
......@@ -116,140 +115,140 @@ func (this *FilterSuite) SetupSuite() {
},
})
this.objects = objects
this.filterFactory = ff
fs.objects = objects
fs.filterFactory = ff
}
func (this *FilterSuite) TearDownSuite() {
func (fs *FilterSuite) TearDownSuite() {
}
// ********************************************* Tests *********************************************
func (this *FilterSuite) Test_FilterIntegersEquals() {
fd := &event.FilterData{"integer", "==", "5"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersEquals() {
fd := &FilterData{"integer", "==", "5"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
break
}
}
this.Equal(arr, this.objects[5:6])
fs.Equal(arr, fs.objects[5:6])
}
func (this *FilterSuite) Test_FilterIntegersLT() {
fd := &event.FilterData{"integer", "<", "5"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersLT() {
fd := &FilterData{"integer", "<", "5"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
this.Equal(arr, this.objects[:5])
fs.Equal(arr, fs.objects[:5])
}
func (this *FilterSuite) Test_FilterIntegersLTEQ() {
fd := &event.FilterData{"integer", "<=", "10"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersLTEQ() {
fd := &FilterData{"integer", "<=", "10"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
this.Equal(arr, this.objects[:11])
fs.Equal(arr, fs.objects[:11])
}
func (this *FilterSuite) Test_FilterIntegersGT() {
fd := &event.FilterData{"integer", ">", "50"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersGT() {
fd := &FilterData{"integer", ">", "50"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
this.Equal(arr, this.objects[51:])
fs.Equal(arr, fs.objects[51:])
}
func (this *FilterSuite) Test_FilterIntegersRange() {
fd0 := &event.FilterData{"integer", ">", "5"}
fd1 := &event.FilterData{"integer", "<", "38"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd0, fd1})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersRange() {
fd0 := &FilterData{"integer", ">", "5"}
fd1 := &FilterData{"integer", "<", "38"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd0, fd1})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
this.Equal(arr, this.objects[6:38])
fs.Equal(arr, fs.objects[6:38])
}
func (this *FilterSuite) Test_FilterIntegersGTEQ() {
fd := &event.FilterData{"integer", ">=", "77"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersGTEQ() {
fd := &FilterData{"integer", ">=", "77"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
this.Equal(arr, this.objects[77:])
fs.Equal(arr, fs.objects[77:])
}
func (this *FilterSuite) Test_FilterIntegersNEQ() {
fd := &event.FilterData{"integer", "!=", "50"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterIntegersNEQ() {
fd := &FilterData{"integer", "!=", "50"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
res := make([]FilterableObject, OBJECTS)
copy(res, this.objects)
copy(res, fs.objects)
res = append(res[:50], res[51:]...)
this.Equal(arr, res)
fs.Equal(arr, res)
}
func (this *FilterSuite) Test_FilterStringEquals() {
fd := &event.FilterData{"string", "==", "string7"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterStringEquals() {
fd := &FilterData{"string", "==", "string7"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
this.Equal(arr, this.objects[7:8])
fs.Equal(arr, fs.objects[7:8])
}
func (this *FilterSuite) Test_FilterStringNEQ() {
fd := &event.FilterData{"string", "!=", "string50"}
filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd})
this.NoError(err)
func (fs *FilterSuite) Test_FilterStringNEQ() {
fd := &FilterData{"string", "!=", "string50"}
filter, err := fs.filterFactory.NewFilter([]*FilterData{fd})
fs.NoError(err)
arr := []FilterableObject{}
for _, o := range this.objects {
for _, o := range fs.objects {
if filter.Match(o) {
arr = append(arr, o)
}
}
res := make([]FilterableObject, OBJECTS)
copy(res, this.objects)
copy(res, fs.objects)
res = append(res[:50], res[51:]...)
this.Equal(arr, res)
fs.Equal(arr, res)
}
// ********************************************* Entrypoint *********************************************
......
// Cross-platform file utils.
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package files
import (
"fmt"
"io/ioutil"
"os"
)
// We don't concern ourselves with executable files here.
const (
FILE_RW = os.FileMode(0666)
FILE_W = os.FileMode(0222)
FILE_R = os.FileMode(0444)
)
func IsWritable(fm os.FileMode) bool {
return fm&2 == 2
}
// Write a file that has both read and write flags set.
func WriteFileRW(fileName string, data []byte) error {
return WriteFile(fileName, data, FILE_RW)
}
// Write file with the read-only flag set.
func WriteFileReadOnly(fileName string, data []byte) error {
return WriteFile(fileName, data, FILE_R)
}
// Write file with the write-only flag set.
func WriteFileWriteOnly(fileName string, data []byte) error {
return WriteFile(fileName, data, FILE_W)
}
// WriteFile.
func WriteFile(fileName string, data []byte, perm os.FileMode) error {
f, err := os.Create(fileName)
if err != nil {
fmt.Println("ERROR OPENING: " + err.Error())
return err
}
defer f.Close()
n, err2 := f.Write(data)
if err2 != nil {
fmt.Println("ERROR WRITING: " + err.Error())
return err
}
if err2 == nil && n < len(data) {
return err
}
return nil
}
// Does the file with the given name exist?
func FileExists(fileName string) bool {
_, err := os.Stat(fileName)
return !os.IsNotExist(err)
}
func IsRegular(fileName string) bool {
fs, err := os.Stat(fileName)
if err != nil {
return false
}
return fs.Mode().IsRegular()
}
func WriteAndBackup(fileName string, data []byte) error {
fs, err := os.Stat(fileName)
fmt.Println("Write and backup")
if err != nil {
if os.IsNotExist(err) {
WriteFileRW(fileName, data)
return nil
}
return err
}
if !fs.Mode().IsRegular() {
return fmt.Errorf("Not a regular file: " + fileName)
}
backupName := fileName + ".bak"
fs, err = os.Stat(backupName)
if err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
// We only work with regular files.
if !fs.Mode().IsRegular() {
return fmt.Errorf(backupName + " is not a regular file.")
}
errR := os.Remove(backupName)
if errR != nil {
return errR
}
}
// Backup file should now be gone.
// Read from original file.
bts, errR := ReadFile(fileName)
if errR != nil {
return errR
}
// Write it to the backup.
errW := WriteFileRW(backupName, bts)
if errW != nil {
return errW
}
// Write new bytes to original.
return WriteFileRW(fileName, data)
}
func ReadFile(fileName string) ([]byte, error) {
return ioutil.ReadFile(fileName)
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package files
import (
"bytes"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
)
var tempFolder = os.TempDir()
var fileData = []byte("aaaaaaaaaaaabbbbbbbbbbbbbbbbbbbaeeeeeeeeeeeeeeaaaaaa")
var fileData2 = []byte("bbbbbbbbbbbb66666666666666666666664bb")
func TestWriteRemove(t *testing.T) {
fileName := "testfile"
write(t, fileName, fileData)
remove(t, fileName)
}
func TestWriteReadRemove(t *testing.T) {
fileName := "testfile"
write(t, fileName, fileData)
readAndCheck(t, fileName, fileData)
remove(t, fileName)
}
func TestRenameRemove(t *testing.T) {
fileName0 := "file0"
fileName1 := "file1"
write(t, fileName0, fileData)
rename(t, fileName0, fileName1)
readAndCheck(t, fileName1, fileData)
remove(t, fileName1)
checkGone(t, fileName0)
}
func TestWriteAndBackup(t *testing.T) {
fileName := "testfile"
backupName := "testfile.bak"
if FileExists(fileName) {
remove(t, fileName)
}
if FileExists(backupName) {
remove(t, backupName)
}
write(t, fileName, fileData)
readAndCheck(t, fileName, fileData)
WriteAndBackup(getName(fileName), fileData2)
readAndCheck(t, backupName, fileData)
remove(t, fileName)
remove(t, backupName)
checkGone(t, fileName)
}
// Helpers
func getName(name string) string {
return path.Join(tempFolder, name)
}
func write(t *testing.T, fileName string, data []byte) {
err := WriteFile(getName(fileName), data, FILE_RW)
assert.NoError(t, err)
}
func readAndCheck(t *testing.T, fileName string, btsIn []byte) {
bts, err := ReadFile(getName(fileName))
assert.NoError(t, err)
assert.True(t, bytes.Equal(bts, btsIn), "Failed to read file data. Written: %s, Read: %s\n", string(fileData), string(bts))
}
func remove(t *testing.T, fileName string) {
err := os.Remove(getName(fileName))
assert.NoError(t, err)
checkGone(t, fileName)
}
func rename(t *testing.T, fileName0, fileName1 string) {
assert.NoError(t, Rename(getName(fileName0), getName(fileName1)))
}
func checkGone(t *testing.T, fileName string) {
name := getName(fileName)
_, err := os.Stat(name)
assert.True(t, os.IsNotExist(err), "File not removed: "+name)
}
// +build !windows
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package files
import "os"
// Rename for linux and macs etc. Don't really care about the rest.
func Rename(oldname, newname string) error {
return os.Rename(oldname, newname)
}
// +build windows
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package files
import (
"fmt"
"os"
)
// TODO finish up.
func Rename(oldname, newname string) error {
// Some extra fluff here.
if fs, err := os.Stat(newname); !os.IsNotExist(err) {
if fs.Mode().IsRegular() && isWritable(fs.Mode().Perm()) {
errRM := os.Remove(newname)
if errRM != nil {
return errRM
}
} else {
return fmt.Errorf("Target exists and cannot be over-written (is a directory or read-only file): " + newname)
}
}
errRN := os.Rename(oldname, newname)
if errRN != nil {
return errRN
}
return nil
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fixtures
import (
"os"
"path"
"io/ioutil"
)
// FileFixtures writes files to a temporary location for use in testing.
type FileFixtures struct {
tempDir string
// If an error has occurred setting up fixtures
Error error
}
// Set up a new FileFixtures object by passing an interlaced list of file names
// and file contents. The file names will be interpreted as relative to some
// temporary root directory that is fixed when allocate() is called on the
// FileFixtures struct.
func NewFileFixtures(identifyingPrefix string) *FileFixtures {
dir, err := ioutil.TempDir("", identifyingPrefix)
return &FileFixtures{
tempDir: dir,
Error: err,
}
}
// Returns the root temporary directory that this FileFixtures will populate and
// clear on RemoveAll()
func (ffs *FileFixtures) TempDir() string {
return ffs.tempDir
}
// Add a file relative to the FileFixtures tempDir using name for the relative
// part of the path.
func (ffs *FileFixtures) AddFile(name, content string) string {
if ffs.Error != nil {
return ""
}
filePath := path.Join(ffs.tempDir, name)
ffs.AddDir(path.Dir(name))
if ffs.Error == nil {
ffs.Error = createWriteClose(filePath, content)
}
return filePath
}
// Ensure that the directory relative to the FileFixtures tempDir exists using
// name for the relative part of the path.
func (ffs *FileFixtures) AddDir(name string) string {
if ffs.Error != nil {
return ""
}
filePath := path.Join(ffs.tempDir, name)
ffs.Error = os.MkdirAll(filePath, 0777)
return filePath
}
// Cleans up the the temporary files (with fire)
func (ffs *FileFixtures) RemoveAll() {
os.RemoveAll(ffs.tempDir)
}
// Create a text file at filename with contents content
func createWriteClose(filename, content string) error {
// We'll create any parent dirs, with permissive permissions
err := os.MkdirAll(path.Dir(filename), 0777)
if err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return err
}
f.WriteString(content)
defer f.Close()
return nil
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
// "fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const (
HTTP_MESSAGES = 300
)
// Send a burst of GET messages to the server.
func TestHttpFlooding(t *testing.T) {
serveProcess, err := NewServeScumbag()
assert.NoError(t, err, "Error creating new Server")
errSS := serveProcess.Start()
assert.NoError(t, errSS, "Scumbag-ed!")
t.Logf("Flooding http requests.")
for i := 0; i < 3; i++ {
err := runHttp()
assert.NoError(t, err)
time.Sleep(200 * time.Millisecond)
}
stopC := serveProcess.StopEventChannel()
errStop := serveProcess.Stop(0)
<-stopC
assert.NoError(t, errStop, "Scumbag-ed!")
}
func runHttp() error {
c := 0
for c < HTTP_MESSAGES {
resp, errG := http.Get("http://localhost:31400/scumbag")
if errG != nil {
return errG
}
c++
resp.Body.Close()
}
return nil
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"encoding/json"
"os"
"runtime"
"github.com/hyperledger/burrow/rpc"
"github.com/hyperledger/burrow/server"
"github.com/gin-gonic/gin"
"github.com/hyperledger/burrow/logging/lifecycle"
"github.com/tendermint/log15"
)
var logger, _ = lifecycle.NewStdErrLogger()
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
log15.Root().SetHandler(log15.LvlFilterHandler(
log15.LvlWarn,
log15.StreamHandler(os.Stdout, log15.TerminalFormat()),
))
gin.SetMode(gin.ReleaseMode)
}
type ScumbagServer struct {
running bool
}
func NewScumbagServer() server.Server {
return &ScumbagServer{}
}
func (this *ScumbagServer) Start(sc *server.ServerConfig, g *gin.Engine) {
g.GET("/scumbag", func(c *gin.Context) {
c.String(200, "Scumbag")
})
this.running = true
}
func (this *ScumbagServer) Running() bool {
return this.running
}
func (this *ScumbagServer) ShutDown() {
// fmt.Println("Scumbag...")
}
type ScumSocketService struct{}
func (this *ScumSocketService) Process(data []byte, session *server.WSSession) {
resp := rpc.NewRPCResponse("1", "Scumbag")
bts, _ := json.Marshal(resp)
session.Write(bts)
}
func NewScumsocketServer(maxConnections uint16) *server.WebSocketServer {
sss := &ScumSocketService{}
return server.NewWebSocketServer(maxConnections, sss, logger)
}
func NewServeScumbag() (*server.ServeProcess, error) {
cfg := server.DefaultServerConfig()
cfg.Bind.Port = uint16(31400)
return server.NewServeProcess(cfg, logger, NewScumbagServer())
}
func NewServeScumSocket(wsServer *server.WebSocketServer) (*server.ServeProcess,
error) {
cfg := server.DefaultServerConfig()
cfg.WebSocket.WebSocketEndpoint = "/scumsocket"
cfg.Bind.Port = uint16(31401)
return server.NewServeProcess(cfg, logger, wsServer)
}
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"testing"
"time"
"github.com/hyperledger/burrow/client"
"github.com/hyperledger/burrow/server"
"github.com/stretchr/testify/assert"
)
const CONNS uint16 = 100
const MESSAGES = 1000
// To keep track of new websocket sessions on the server.
type SessionCounter struct {
opened int
closed int
}
func (this *SessionCounter) Run(oChan, cChan <-chan *server.WSSession) {
go func() {
for {
select {
case <-oChan:
this.opened++
break
case <-cChan:
this.closed++
break
}
}
}()
}
func (this *SessionCounter) Report() (int, int, int) {
return this.opened, this.closed, this.opened - this.closed
}
// Testing to ensure that websocket server does not crash, and that it
// cleans up after itself.
func TestWsFlooding(t *testing.T) {
// New websocket server.
wsServer := NewScumsocketServer(CONNS)
// Keep track of sessions.
sc := &SessionCounter{}
// Register the observer.
oChan := wsServer.SessionManager().SessionOpenEventChannel()
cChan := wsServer.SessionManager().SessionCloseEventChannel()
sc.Run(oChan, cChan)
serveProcess, err := NewServeScumSocket(wsServer)
assert.NoError(t, err, "Failed to serve new websocket.")
errServe := serveProcess.Start()
assert.NoError(t, errServe, "ScumSocketed!")
t.Logf("Flooding...")
// Run. Blocks.
errRun := runWs()
stopC := serveProcess.StopEventChannel()
errStop := serveProcess.Stop(0)
<-stopC
assert.NoError(t, errRun, "ScumSocketed!")
assert.NoError(t, errStop, "ScumSocketed!")
o, c, a := sc.Report()
assert.Equal(t, uint16(o), CONNS, "Server registered '%d' opened conns out of '%d'", o, CONNS)
assert.Equal(t, uint16(c), CONNS, "Server registered '%d' closed conns out of '%d'", c, CONNS)
assert.Equal(t, uint16(a), uint16(0), "Server registered '%d' conns still active after shutting down.", a)
}
func runWs() error {
doneChan := make(chan bool)
errChan := make(chan error)
for i := uint16(0); i < CONNS; i++ {
go wsClient(doneChan, errChan)
}
runners := uint16(0)
for runners < CONNS {
select {
case <-doneChan:
runners++
case err := <-errChan:
return err
}
}
return nil
}
func wsClient(doneChan chan bool, errChan chan error) {
client := client.NewWSClient("ws://localhost:31401/scumsocket")
_, err := client.Dial()
if err != nil {
errChan <- err
return
}
readChan := client.StartRead()
i := 0
for i < MESSAGES {
client.WriteMsg([]byte("test"))
<-readChan
i++
}
client.Close()
time.Sleep(100 * time.Millisecond)
doneChan <- true
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment