Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
00878e5b
Commit
00878e5b
authored
Nov 12, 2014
by
Taylor Gerring
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Convert trie tests to gocheck
parent
1d866b5e
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
119 additions
and
164 deletions
+119
-164
main_test.go
trie/main_test.go
+8
-0
trie_test.go
trie/trie_test.go
+111
-164
No files found.
trie/main_test.go
0 → 100644
View file @
00878e5b
package
trie
import
(
checker
"gopkg.in/check.v1"
"testing"
)
func
Test
(
t
*
testing
.
T
)
{
checker
.
TestingT
(
t
)
}
trie/trie_test.go
View file @
00878e5b
package
trie
package
trie
import
(
import
(
"bytes"
"encoding/hex"
"encoding/hex"
"encoding/json"
"encoding/json"
"fmt"
"fmt"
checker
"gopkg.in/check.v1"
"io/ioutil"
"io/ioutil"
"math/rand"
"math/rand"
"net/http"
"net/http"
"reflect"
"testing"
"time"
"time"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/ethutil"
...
@@ -17,6 +15,11 @@ import (
...
@@ -17,6 +15,11 @@ import (
const
LONG_WORD
=
"1234567890abcdefghijklmnopqrstuvwxxzABCEFGHIJKLMNOPQRSTUVWXYZ"
const
LONG_WORD
=
"1234567890abcdefghijklmnopqrstuvwxxzABCEFGHIJKLMNOPQRSTUVWXYZ"
type
TrieSuite
struct
{
db
*
MemDatabase
trie
*
Trie
}
type
MemDatabase
struct
{
type
MemDatabase
struct
{
db
map
[
string
][]
byte
db
map
[
string
][]
byte
}
}
...
@@ -44,140 +47,97 @@ func NewTrie() (*MemDatabase, *Trie) {
...
@@ -44,140 +47,97 @@ func NewTrie() (*MemDatabase, *Trie) {
return
db
,
New
(
db
,
""
)
return
db
,
New
(
db
,
""
)
}
}
func
TestTrieSync
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
SetUpTest
(
c
*
checker
.
C
)
{
db
,
trie
:=
NewTrie
()
s
.
db
,
s
.
trie
=
NewTrie
()
trie
.
Update
(
"dog"
,
LONG_WORD
)
if
len
(
db
.
db
)
!=
0
{
t
.
Error
(
"Expected no data in database"
)
}
trie
.
Sync
()
if
len
(
db
.
db
)
==
0
{
t
.
Error
(
"Expected data to be persisted"
)
}
}
}
func
TestTrieDirtyTracking
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieSync
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
(
)
s
.
trie
.
Update
(
"dog"
,
LONG_WORD
)
trie
.
Update
(
"dog"
,
LONG_WORD
)
c
.
Assert
(
s
.
db
.
db
,
checker
.
HasLen
,
0
,
checker
.
Commentf
(
"Expected no data in database"
)
)
if
!
trie
.
cache
.
IsDirty
{
s
.
trie
.
Sync
()
t
.
Error
(
"Expected trie to be dirty"
)
c
.
Assert
(
s
.
db
.
db
,
checker
.
HasLen
,
3
)
}
}
trie
.
Sync
()
func
(
s
*
TrieSuite
)
TestTrieDirtyTracking
(
c
*
checker
.
C
)
{
if
trie
.
cache
.
IsDirty
{
s
.
trie
.
Update
(
"dog"
,
LONG_WORD
)
t
.
Error
(
"Expected trie not to be dirty"
)
c
.
Assert
(
s
.
trie
.
cache
.
IsDirty
,
checker
.
Equals
,
true
,
checker
.
Commentf
(
"Expected no data in database"
))
}
trie
.
Update
(
"test"
,
LONG_WORD
)
s
.
trie
.
Sync
()
trie
.
cache
.
Undo
()
c
.
Assert
(
s
.
trie
.
cache
.
IsDirty
,
checker
.
Equals
,
false
,
checker
.
Commentf
(
"Expected trie to be dirty"
))
if
trie
.
cache
.
IsDirty
{
t
.
Error
(
"Expected trie not to be dirty"
)
}
s
.
trie
.
Update
(
"test"
,
LONG_WORD
)
s
.
trie
.
cache
.
Undo
()
c
.
Assert
(
s
.
trie
.
cache
.
IsDirty
,
checker
.
Equals
,
false
)
}
}
func
TestTrieReset
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieReset
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
)
c
.
Assert
(
s
.
trie
.
cache
.
nodes
,
checker
.
HasLen
,
1
,
checker
.
Commentf
(
"Expected cached nodes"
))
trie
.
Update
(
"cat"
,
LONG_WORD
)
if
len
(
trie
.
cache
.
nodes
)
==
0
{
t
.
Error
(
"Expected cached nodes"
)
}
trie
.
cache
.
Undo
()
s
.
trie
.
cache
.
Undo
()
c
.
Assert
(
s
.
trie
.
cache
.
nodes
,
checker
.
HasLen
,
0
,
checker
.
Commentf
(
"Expected no nodes after undo"
))
if
len
(
trie
.
cache
.
nodes
)
!=
0
{
t
.
Error
(
"Expected no nodes after undo"
)
}
}
}
func
TestTrieGet
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieGet
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
)
x
:=
s
.
trie
.
Get
(
"cat"
)
trie
.
Update
(
"cat"
,
LONG_WORD
)
c
.
Assert
(
x
,
checker
.
DeepEquals
,
LONG_WORD
)
x
:=
trie
.
Get
(
"cat"
)
if
x
!=
LONG_WORD
{
t
.
Error
(
"expected %s, got %s"
,
LONG_WORD
,
x
)
}
}
}
func
TestTrieUpdating
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieUpdating
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
)
trie
.
Update
(
"cat"
,
LONG_WORD
)
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
+
"1"
)
trie
.
Update
(
"cat"
,
LONG_WORD
+
"1"
)
x
:=
s
.
trie
.
Get
(
"cat"
)
x
:=
trie
.
Get
(
"cat"
)
c
.
Assert
(
x
,
checker
.
DeepEquals
,
LONG_WORD
+
"1"
)
if
x
!=
LONG_WORD
+
"1"
{
t
.
Error
(
"expected %S, got %s"
,
LONG_WORD
+
"1"
,
x
)
}
}
}
func
TestTrieCmp
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieCmp
(
c
*
checker
.
C
)
{
_
,
trie1
:=
NewTrie
()
_
,
trie1
:=
NewTrie
()
_
,
trie2
:=
NewTrie
()
_
,
trie2
:=
NewTrie
()
trie1
.
Update
(
"doge"
,
LONG_WORD
)
trie1
.
Update
(
"doge"
,
LONG_WORD
)
trie2
.
Update
(
"doge"
,
LONG_WORD
)
trie2
.
Update
(
"doge"
,
LONG_WORD
)
if
!
trie1
.
Cmp
(
trie2
)
{
c
.
Assert
(
trie1
,
checker
.
DeepEquals
,
trie2
)
t
.
Error
(
"Expected tries to be equal"
)
}
trie1
.
Update
(
"dog"
,
LONG_WORD
)
trie1
.
Update
(
"dog"
,
LONG_WORD
)
trie2
.
Update
(
"cat"
,
LONG_WORD
)
trie2
.
Update
(
"cat"
,
LONG_WORD
)
if
trie1
.
Cmp
(
trie2
)
{
c
.
Assert
(
trie1
,
checker
.
Not
(
checker
.
DeepEquals
),
trie2
)
t
.
Errorf
(
"Expected tries not to be equal %x %x"
,
trie1
.
Root
,
trie2
.
Root
)
}
}
}
func
TestTrieDelete
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieDelete
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
)
trie
.
Update
(
"cat"
,
LONG_WORD
)
exp
:=
s
.
trie
.
Root
exp
:=
trie
.
Root
s
.
trie
.
Update
(
"dog"
,
LONG_WORD
)
trie
.
Update
(
"dog"
,
LONG_WORD
)
s
.
trie
.
Delete
(
"dog"
)
trie
.
Delete
(
"dog"
)
c
.
Assert
(
s
.
trie
.
Root
,
checker
.
DeepEquals
,
exp
)
if
!
reflect
.
DeepEqual
(
exp
,
trie
.
Root
)
{
t
.
Errorf
(
"Expected tries to be equal %x : %x"
,
exp
,
trie
.
Root
)
s
.
trie
.
Update
(
"dog"
,
LONG_WORD
)
}
exp
=
s
.
trie
.
Root
s
.
trie
.
Update
(
"dude"
,
LONG_WORD
)
trie
.
Update
(
"dog"
,
LONG_WORD
)
s
.
trie
.
Delete
(
"dude"
)
exp
=
trie
.
Root
c
.
Assert
(
s
.
trie
.
Root
,
checker
.
DeepEquals
,
exp
)
trie
.
Update
(
"dude"
,
LONG_WORD
)
trie
.
Delete
(
"dude"
)
if
!
reflect
.
DeepEqual
(
exp
,
trie
.
Root
)
{
t
.
Errorf
(
"Expected tries to be equal %x : %x"
,
exp
,
trie
.
Root
)
}
}
}
func
TestTrieDeleteWithValue
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTrieDeleteWithValue
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"c"
,
LONG_WORD
)
trie
.
Update
(
"c"
,
LONG_WORD
)
exp
:=
s
.
trie
.
Root
exp
:=
trie
.
Root
s
.
trie
.
Update
(
"ca"
,
LONG_WORD
)
trie
.
Update
(
"ca"
,
LONG_WORD
)
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
)
trie
.
Update
(
"cat"
,
LONG_WORD
)
s
.
trie
.
Delete
(
"ca"
)
trie
.
Delete
(
"ca"
)
s
.
trie
.
Delete
(
"cat"
)
trie
.
Delete
(
"cat"
)
c
.
Assert
(
s
.
trie
.
Root
,
checker
.
DeepEquals
,
exp
)
if
!
reflect
.
DeepEqual
(
exp
,
trie
.
Root
)
{
t
.
Errorf
(
"Expected tries to be equal %x : %x"
,
exp
,
trie
.
Root
)
}
}
}
func
TestTriePurge
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTriePurge
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"c"
,
LONG_WORD
)
trie
.
Update
(
"c"
,
LONG_WORD
)
s
.
trie
.
Update
(
"ca"
,
LONG_WORD
)
trie
.
Update
(
"ca"
,
LONG_WORD
)
s
.
trie
.
Update
(
"cat"
,
LONG_WORD
)
trie
.
Update
(
"cat"
,
LONG_WORD
)
lenBefore
:=
len
(
trie
.
cache
.
nodes
)
it
:=
trie
.
NewIterator
()
if
num
:=
it
.
Purge
();
num
!=
3
{
t
.
Errorf
(
"Expected purge to return 3, got %d"
,
num
)
}
if
lenBefore
==
len
(
trie
.
cache
.
nodes
)
{
lenBefore
:=
len
(
s
.
trie
.
cache
.
nodes
)
t
.
Errorf
(
"Expected cached nodes to be deleted"
)
it
:=
s
.
trie
.
NewIterator
()
}
num
:=
it
.
Purge
()
c
.
Assert
(
num
,
checker
.
Equals
,
3
)
c
.
Assert
(
len
(
s
.
trie
.
cache
.
nodes
),
checker
.
Equals
,
lenBefore
)
}
}
func
h
(
str
string
)
string
{
func
h
(
str
string
)
string
{
...
@@ -199,23 +159,23 @@ func get(in string) (out string) {
...
@@ -199,23 +159,23 @@ func get(in string) (out string) {
return
return
}
}
type
Test
struct
{
type
T
rieT
est
struct
{
Name
string
Name
string
In
map
[
string
]
string
In
map
[
string
]
string
Root
string
Root
string
}
}
func
CreateTest
(
name
string
,
data
[]
byte
)
(
Test
,
error
)
{
func
CreateTest
(
name
string
,
data
[]
byte
)
(
T
rieT
est
,
error
)
{
t
:=
Test
{
Name
:
name
}
t
:=
T
rieT
est
{
Name
:
name
}
err
:=
json
.
Unmarshal
(
data
,
&
t
)
err
:=
json
.
Unmarshal
(
data
,
&
t
)
if
err
!=
nil
{
if
err
!=
nil
{
return
Test
{},
fmt
.
Errorf
(
"%v"
,
err
)
return
T
rieT
est
{},
fmt
.
Errorf
(
"%v"
,
err
)
}
}
return
t
,
nil
return
t
,
nil
}
}
func
CreateTests
(
uri
string
,
cb
func
(
T
est
))
map
[
string
]
Test
{
func
CreateTests
(
uri
string
,
cb
func
(
T
rieTest
))
map
[
string
]
Trie
Test
{
resp
,
err
:=
http
.
Get
(
uri
)
resp
,
err
:=
http
.
Get
(
uri
)
if
err
!=
nil
{
if
err
!=
nil
{
panic
(
err
)
panic
(
err
)
...
@@ -230,7 +190,7 @@ func CreateTests(uri string, cb func(Test)) map[string]Test {
...
@@ -230,7 +190,7 @@ func CreateTests(uri string, cb func(Test)) map[string]Test {
panic
(
err
)
panic
(
err
)
}
}
tests
:=
make
(
map
[
string
]
Test
)
tests
:=
make
(
map
[
string
]
T
rieT
est
)
for
name
,
testData
:=
range
objmap
{
for
name
,
testData
:=
range
objmap
{
test
,
err
:=
CreateTest
(
name
,
*
testData
)
test
,
err
:=
CreateTest
(
name
,
*
testData
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -274,7 +234,7 @@ func RandomData() [][]string {
...
@@ -274,7 +234,7 @@ func RandomData() [][]string {
const
MaxTest
=
1000
const
MaxTest
=
1000
// This test insert data in random order and seeks to find indifferences between the different tries
// This test insert data in random order and seeks to find indifferences between the different tries
func
TestRegression
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestRegression
(
c
*
checker
.
C
)
{
rand
.
Seed
(
time
.
Now
()
.
Unix
())
rand
.
Seed
(
time
.
Now
()
.
Unix
())
roots
:=
make
(
map
[
string
]
int
)
roots
:=
make
(
map
[
string
]
int
)
...
@@ -290,34 +250,33 @@ func TestRegression(t *testing.T) {
...
@@ -290,34 +250,33 @@ func TestRegression(t *testing.T) {
roots
[
string
(
trie
.
Root
.
([]
byte
))]
+=
1
roots
[
string
(
trie
.
Root
.
([]
byte
))]
+=
1
}
}
if
len
(
roots
)
>
1
{
c
.
Assert
(
len
(
roots
)
<=
1
,
checker
.
Equals
,
true
)
for
root
,
num
:=
range
roots
{
// if len(roots) > 1 {
t
.
Errorf
(
"%x => %d
\n
"
,
root
,
num
)
// for root, num := range roots {
}
// t.Errorf("%x => %d\n", root, num)
}
// }
// }
}
}
func
TestDelete
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestDelete
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"a"
,
"jeffreytestlongstring"
)
s
.
trie
.
Update
(
"aa"
,
"otherstring"
)
trie
.
Update
(
"a"
,
"jeffreytestlongstring"
)
s
.
trie
.
Update
(
"aaa"
,
"othermorestring"
)
trie
.
Update
(
"aa"
,
"otherstring"
)
s
.
trie
.
Update
(
"aabbbbccc"
,
"hithere"
)
trie
.
Update
(
"aaa"
,
"othermorestring"
)
s
.
trie
.
Update
(
"abbcccdd"
,
"hstanoehutnaheoustnh"
)
trie
.
Update
(
"aabbbbccc"
,
"hithere"
)
s
.
trie
.
Update
(
"rnthaoeuabbcccdd"
,
"hstanoehutnaheoustnh"
)
trie
.
Update
(
"abbcccdd"
,
"hstanoehutnaheoustnh"
)
s
.
trie
.
Update
(
"rneuabbcccdd"
,
"hstanoehutnaheoustnh"
)
trie
.
Update
(
"rnthaoeuabbcccdd"
,
"hstanoehutnaheoustnh"
)
s
.
trie
.
Update
(
"rneuabboeusntahoeucccdd"
,
"hstanoehutnaheoustnh"
)
trie
.
Update
(
"rneuabbcccdd"
,
"hstanoehutnaheoustnh"
)
s
.
trie
.
Update
(
"rnxabboeusntahoeucccdd"
,
"hstanoehutnaheoustnh"
)
trie
.
Update
(
"rneuabboeusntahoeucccdd"
,
"hstanoehutnaheoustnh"
)
s
.
trie
.
Delete
(
"aaboaestnuhbccc"
)
trie
.
Update
(
"rnxabboeusntahoeucccdd"
,
"hstanoehutnaheoustnh"
)
s
.
trie
.
Delete
(
"a"
)
trie
.
Delete
(
"aaboaestnuhbccc"
)
s
.
trie
.
Update
(
"a"
,
"nthaonethaosentuh"
)
trie
.
Delete
(
"a"
)
s
.
trie
.
Update
(
"c"
,
"shtaosntehua"
)
trie
.
Update
(
"a"
,
"nthaonethaosentuh"
)
s
.
trie
.
Delete
(
"a"
)
trie
.
Update
(
"c"
,
"shtaosntehua"
)
s
.
trie
.
Update
(
"aaaa"
,
"testmegood"
)
trie
.
Delete
(
"a"
)
trie
.
Update
(
"aaaa"
,
"testmegood"
)
_
,
t2
:=
NewTrie
()
_
,
t2
:=
NewTrie
()
trie
.
NewIterator
()
.
Each
(
func
(
key
string
,
v
*
ethutil
.
Value
)
{
s
.
trie
.
NewIterator
()
.
Each
(
func
(
key
string
,
v
*
ethutil
.
Value
)
{
if
key
==
"aaaa"
{
if
key
==
"aaaa"
{
t2
.
Update
(
key
,
v
.
Str
())
t2
.
Update
(
key
,
v
.
Str
())
}
else
{
}
else
{
...
@@ -325,27 +284,22 @@ func TestDelete(t *testing.T) {
...
@@ -325,27 +284,22 @@ func TestDelete(t *testing.T) {
}
}
})
})
a
:=
ethutil
.
NewValue
(
trie
.
Root
)
.
Bytes
()
a
:=
ethutil
.
NewValue
(
s
.
trie
.
Root
)
.
Bytes
()
b
:=
ethutil
.
NewValue
(
t2
.
Root
)
.
Bytes
()
b
:=
ethutil
.
NewValue
(
t2
.
Root
)
.
Bytes
()
if
bytes
.
Compare
(
a
,
b
)
!=
0
{
c
.
Assert
(
a
,
checker
.
DeepEquals
,
b
)
t
.
Errorf
(
"Expected %x and %x to be equal"
,
a
,
b
)
}
}
}
func
TestTerminator
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestTerminator
(
c
*
checker
.
C
)
{
key
:=
CompactDecode
(
"hello"
)
key
:=
CompactDecode
(
"hello"
)
if
!
HasTerm
(
key
)
{
c
.
Assert
(
HasTerm
(
key
),
checker
.
Equals
,
true
,
checker
.
Commentf
(
"Expected %v to have a terminator"
,
key
))
t
.
Errorf
(
"Expected %v to have a terminator"
,
key
)
}
}
}
func
TestIt
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestIt
(
c
*
checker
.
C
)
{
_
,
trie
:=
NewTrie
()
s
.
trie
.
Update
(
"cat"
,
"cat"
)
trie
.
Update
(
"cat"
,
"cat"
)
s
.
trie
.
Update
(
"doge"
,
"doge"
)
trie
.
Update
(
"doge"
,
"doge"
)
s
.
trie
.
Update
(
"wallace"
,
"wallace"
)
trie
.
Update
(
"wallace"
,
"wallace"
)
it
:=
s
.
trie
.
Iterator
()
it
:=
trie
.
Iterator
()
inputs
:=
[]
struct
{
inputs
:=
[]
struct
{
In
,
Out
string
In
,
Out
string
...
@@ -361,23 +315,16 @@ func TestIt(t *testing.T) {
...
@@ -361,23 +315,16 @@ func TestIt(t *testing.T) {
for
_
,
test
:=
range
inputs
{
for
_
,
test
:=
range
inputs
{
res
:=
string
(
it
.
Next
(
test
.
In
))
res
:=
string
(
it
.
Next
(
test
.
In
))
if
res
!=
test
.
Out
{
c
.
Assert
(
res
,
checker
.
Equals
,
test
.
Out
)
t
.
Errorf
(
test
.
In
,
"failed. Got"
,
res
,
"Expected"
,
test
.
Out
)
}
}
}
}
}
func
TestBeginsWith
(
t
*
testing
.
T
)
{
func
(
s
*
TrieSuite
)
TestBeginsWith
(
c
*
checker
.
C
)
{
a
:=
CompactDecode
(
"hello"
)
a
:=
CompactDecode
(
"hello"
)
b
:=
CompactDecode
(
"hel"
)
b
:=
CompactDecode
(
"hel"
)
if
BeginsWith
(
a
,
b
)
{
c
.
Assert
(
BeginsWith
(
a
,
b
),
checker
.
Equals
,
false
)
t
.
Errorf
(
"Expected %x to begin with %x"
,
a
,
b
)
c
.
Assert
(
BeginsWith
(
b
,
a
),
checker
.
Equals
,
true
)
}
if
BeginsWith
(
b
,
a
)
{
t
.
Errorf
(
"Expected %x not to begin with %x"
,
b
,
a
)
}
}
}
/*
/*
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment