Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
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
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
f21cf887
Commit
f21cf887
authored
Sep 27, 2017
by
m1093782566
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement delete real server for fakeIPVS and add UTs
parent
4f2d7b93
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
299 additions
and
16 deletions
+299
-16
ipvs.go
pkg/util/ipvs/ipvs.go
+10
-2
ipvs_test.go
pkg/util/ipvs/ipvs_test.go
+101
-0
BUILD
pkg/util/ipvs/testing/BUILD
+8
-0
fake.go
pkg/util/ipvs/testing/fake.go
+31
-14
fake_test.go
pkg/util/ipvs/testing/fake_test.go
+149
-0
No files found.
pkg/util/ipvs/ipvs.go
View file @
f21cf887
...
@@ -89,6 +89,14 @@ type RealServer struct {
...
@@ -89,6 +89,14 @@ type RealServer struct {
Weight
int
Weight
int
}
}
func
(
dest
*
RealServer
)
String
()
string
{
func
(
rs
*
RealServer
)
String
()
string
{
return
net
.
JoinHostPort
(
dest
.
Address
.
String
(),
strconv
.
Itoa
(
int
(
dest
.
Port
)))
return
net
.
JoinHostPort
(
rs
.
Address
.
String
(),
strconv
.
Itoa
(
int
(
rs
.
Port
)))
}
// Equal check the equality of real server.
// We don't use struct == since it doesn't work because of slice.
func
(
rs
*
RealServer
)
Equal
(
other
*
RealServer
)
bool
{
return
rs
.
Address
.
Equal
(
other
.
Address
)
&&
rs
.
Port
==
other
.
Port
&&
rs
.
Weight
==
other
.
Weight
}
}
pkg/util/ipvs/ipvs_test.go
View file @
f21cf887
...
@@ -198,6 +198,107 @@ func TestVirtualServerEqual(t *testing.T) {
...
@@ -198,6 +198,107 @@ func TestVirtualServerEqual(t *testing.T) {
}
}
}
}
func
TestRealServerEqual
(
t
*
testing
.
T
)
{
Tests
:=
[]
struct
{
rsA
*
RealServer
rsB
*
RealServer
equal
bool
reason
string
}{
{
rsA
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"10.20.30.40"
),
Port
:
80
,
Weight
:
1
,
},
rsB
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"10.20.30.41"
),
Port
:
80
,
Weight
:
1
,
},
equal
:
false
,
reason
:
"IPv4 address not equal"
,
},
{
rsA
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"2012::beef"
),
Port
:
80
,
Weight
:
1
,
},
rsB
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"2017::beef"
),
Port
:
80
,
Weight
:
1
,
},
equal
:
false
,
reason
:
"IPv6 address not equal"
,
},
{
rsA
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"2012::beef"
),
Port
:
80
,
Weight
:
1
,
},
rsB
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"2012::beef"
),
Port
:
8080
,
Weight
:
1
,
},
equal
:
false
,
reason
:
"Port not equal"
,
},
{
rsA
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"10.20.30.40"
),
Port
:
8080
,
Weight
:
1
,
},
rsB
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"10.20.30.40"
),
Port
:
8080
,
Weight
:
10
,
},
equal
:
false
,
reason
:
"Weight not equal"
,
},
{
rsA
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"1.2.3.4"
),
Port
:
3080
,
Weight
:
10
,
},
rsB
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"1.2.3.4"
),
Port
:
3080
,
Weight
:
10
,
},
equal
:
true
,
reason
:
"All fields equal"
,
},
{
rsA
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"2012::beef"
),
Port
:
3080
,
Weight
:
10
,
},
rsB
:
&
RealServer
{
Address
:
net
.
ParseIP
(
"2012::beef"
),
Port
:
3080
,
Weight
:
10
,
},
equal
:
true
,
reason
:
"All fields equal"
,
},
}
for
i
:=
range
Tests
{
equal
:=
Tests
[
i
]
.
rsA
.
Equal
(
Tests
[
i
]
.
rsB
)
if
equal
!=
Tests
[
i
]
.
equal
{
t
.
Errorf
(
"case: %d got %v, expected %v, reason: %s"
,
i
,
equal
,
Tests
[
i
]
.
equal
,
Tests
[
i
]
.
reason
)
}
}
}
func
TestFrontendServiceString
(
t
*
testing
.
T
)
{
func
TestFrontendServiceString
(
t
*
testing
.
T
)
{
Tests
:=
[]
struct
{
Tests
:=
[]
struct
{
svc
*
VirtualServer
svc
*
VirtualServer
...
...
pkg/util/ipvs/testing/BUILD
View file @
f21cf887
...
@@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
...
@@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load(
load(
"@io_bazel_rules_go//go:def.bzl",
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_library",
"go_test",
)
)
go_library(
go_library(
...
@@ -23,3 +24,10 @@ filegroup(
...
@@ -23,3 +24,10 @@ filegroup(
srcs = [":package-srcs"],
srcs = [":package-srcs"],
tags = ["automanaged"],
tags = ["automanaged"],
)
)
go_test(
name = "go_default_test",
srcs = ["fake_test.go"],
library = ":go_default_library",
deps = ["//pkg/util/ipvs:go_default_library"],
)
pkg/util/ipvs/testing/fake.go
View file @
f21cf887
...
@@ -39,7 +39,7 @@ func (s *serviceKey) String() string {
...
@@ -39,7 +39,7 @@ func (s *serviceKey) String() string {
return
fmt
.
Sprintf
(
"%s:%d/%s"
,
s
.
IP
,
s
.
Port
,
s
.
Protocol
)
return
fmt
.
Sprintf
(
"%s:%d/%s"
,
s
.
IP
,
s
.
Port
,
s
.
Protocol
)
}
}
//NewFake creates a fake ipvs
strucuter
//NewFake creates a fake ipvs
implementation - a cache store.
func
NewFake
()
*
FakeIPVS
{
func
NewFake
()
*
FakeIPVS
{
return
&
FakeIPVS
{
return
&
FakeIPVS
{
Services
:
make
(
map
[
serviceKey
]
*
utilipvs
.
VirtualServer
),
Services
:
make
(
map
[
serviceKey
]
*
utilipvs
.
VirtualServer
),
...
@@ -55,20 +55,20 @@ func toServiceKey(serv *utilipvs.VirtualServer) serviceKey {
...
@@ -55,20 +55,20 @@ func toServiceKey(serv *utilipvs.VirtualServer) serviceKey {
}
}
}
}
//EnsureVirtualServerAddressBind is a
fake
implementation
//EnsureVirtualServerAddressBind is a
n empty
implementation
func
(
*
FakeIPVS
)
EnsureVirtualServerAddressBind
(
serv
*
utilipvs
.
VirtualServer
,
dev
string
)
(
exist
bool
,
err
error
)
{
func
(
*
FakeIPVS
)
EnsureVirtualServerAddressBind
(
serv
*
utilipvs
.
VirtualServer
,
dev
string
)
(
exist
bool
,
err
error
)
{
return
true
,
nil
return
true
,
nil
}
}
//UnbindVirtualServerAddress is a
fake
implementation
//UnbindVirtualServerAddress is a
n empty
implementation
func
(
*
FakeIPVS
)
UnbindVirtualServerAddress
(
serv
*
utilipvs
.
VirtualServer
,
dev
string
)
error
{
func
(
*
FakeIPVS
)
UnbindVirtualServerAddress
(
serv
*
utilipvs
.
VirtualServer
,
dev
string
)
error
{
return
nil
return
nil
}
}
//AddVirtualServer is a fake implementation
//AddVirtualServer is a fake implementation
, it simply adds the VirtualServer into the cache store.
func
(
f
*
FakeIPVS
)
AddVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
error
{
func
(
f
*
FakeIPVS
)
AddVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
error
{
if
serv
==
nil
{
if
serv
==
nil
{
return
fmt
.
Errorf
(
"Failed to add
service: service
can't be nil"
)
return
fmt
.
Errorf
(
"Failed to add
virtual server, error: virtual server
can't be nil"
)
}
}
key
:=
toServiceKey
(
serv
)
key
:=
toServiceKey
(
serv
)
f
.
Services
[
key
]
=
serv
f
.
Services
[
key
]
=
serv
...
@@ -77,7 +77,7 @@ func (f *FakeIPVS) AddVirtualServer(serv *utilipvs.VirtualServer) error {
...
@@ -77,7 +77,7 @@ func (f *FakeIPVS) AddVirtualServer(serv *utilipvs.VirtualServer) error {
return
nil
return
nil
}
}
//UpdateVirtualServer is a
fake
implementation
//UpdateVirtualServer is a
n empty
implementation
func
(
f
*
FakeIPVS
)
UpdateVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
error
{
func
(
f
*
FakeIPVS
)
UpdateVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
error
{
if
serv
==
nil
{
if
serv
==
nil
{
return
fmt
.
Errorf
(
"Failed to update service, service can't be nil"
)
return
fmt
.
Errorf
(
"Failed to update service, service can't be nil"
)
...
@@ -85,7 +85,7 @@ func (f *FakeIPVS) UpdateVirtualServer(serv *utilipvs.VirtualServer) error {
...
@@ -85,7 +85,7 @@ func (f *FakeIPVS) UpdateVirtualServer(serv *utilipvs.VirtualServer) error {
return
nil
return
nil
}
}
//DeleteVirtualServer is a fake implementation
//DeleteVirtualServer is a fake implementation
, it simply deletes the VirtualServer from the cache store.
func
(
f
*
FakeIPVS
)
DeleteVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
error
{
func
(
f
*
FakeIPVS
)
DeleteVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
error
{
if
serv
==
nil
{
if
serv
==
nil
{
return
fmt
.
Errorf
(
"Failed to delete service: service can't be nil"
)
return
fmt
.
Errorf
(
"Failed to delete service: service can't be nil"
)
...
@@ -97,7 +97,7 @@ func (f *FakeIPVS) DeleteVirtualServer(serv *utilipvs.VirtualServer) error {
...
@@ -97,7 +97,7 @@ func (f *FakeIPVS) DeleteVirtualServer(serv *utilipvs.VirtualServer) error {
return
nil
return
nil
}
}
//GetVirtualServer is a fake implementation
//GetVirtualServer is a fake implementation
, it tries to find a specific VirtualServer from the cache store.
func
(
f
*
FakeIPVS
)
GetVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
(
*
utilipvs
.
VirtualServer
,
error
)
{
func
(
f
*
FakeIPVS
)
GetVirtualServer
(
serv
*
utilipvs
.
VirtualServer
)
(
*
utilipvs
.
VirtualServer
,
error
)
{
if
serv
==
nil
{
if
serv
==
nil
{
return
nil
,
fmt
.
Errorf
(
"Failed to get service: service can't be nil"
)
return
nil
,
fmt
.
Errorf
(
"Failed to get service: service can't be nil"
)
...
@@ -110,7 +110,7 @@ func (f *FakeIPVS) GetVirtualServer(serv *utilipvs.VirtualServer) (*utilipvs.Vir
...
@@ -110,7 +110,7 @@ func (f *FakeIPVS) GetVirtualServer(serv *utilipvs.VirtualServer) (*utilipvs.Vir
return
nil
,
fmt
.
Errorf
(
"Not found serv: %v"
,
key
.
String
())
return
nil
,
fmt
.
Errorf
(
"Not found serv: %v"
,
key
.
String
())
}
}
//GetVirtualServers is a fake implementation
//GetVirtualServers is a fake implementation
, it simply returns all VirtualServers in the cache store.
func
(
f
*
FakeIPVS
)
GetVirtualServers
()
([]
*
utilipvs
.
VirtualServer
,
error
)
{
func
(
f
*
FakeIPVS
)
GetVirtualServers
()
([]
*
utilipvs
.
VirtualServer
,
error
)
{
res
:=
make
([]
*
utilipvs
.
VirtualServer
,
0
)
res
:=
make
([]
*
utilipvs
.
VirtualServer
,
0
)
for
_
,
svc
:=
range
f
.
Services
{
for
_
,
svc
:=
range
f
.
Services
{
...
@@ -119,7 +119,7 @@ func (f *FakeIPVS) GetVirtualServers() ([]*utilipvs.VirtualServer, error) {
...
@@ -119,7 +119,7 @@ func (f *FakeIPVS) GetVirtualServers() ([]*utilipvs.VirtualServer, error) {
return
res
,
nil
return
res
,
nil
}
}
//Flush is a fake implementation
//Flush is a fake implementation
, it simply clears the cache store.
func
(
f
*
FakeIPVS
)
Flush
()
error
{
func
(
f
*
FakeIPVS
)
Flush
()
error
{
// directly drop old data
// directly drop old data
f
.
Services
=
nil
f
.
Services
=
nil
...
@@ -127,7 +127,7 @@ func (f *FakeIPVS) Flush() error {
...
@@ -127,7 +127,7 @@ func (f *FakeIPVS) Flush() error {
return
nil
return
nil
}
}
//AddRealServer is a fake implementation
//AddRealServer is a fake implementation
, it simply creates a RealServer for a VirtualServer in the cache store.
func
(
f
*
FakeIPVS
)
AddRealServer
(
serv
*
utilipvs
.
VirtualServer
,
dest
*
utilipvs
.
RealServer
)
error
{
func
(
f
*
FakeIPVS
)
AddRealServer
(
serv
*
utilipvs
.
VirtualServer
,
dest
*
utilipvs
.
RealServer
)
error
{
if
serv
==
nil
||
dest
==
nil
{
if
serv
==
nil
||
dest
==
nil
{
return
fmt
.
Errorf
(
"Failed to add destination for service, neither service nor destination shouldn't be nil"
)
return
fmt
.
Errorf
(
"Failed to add destination for service, neither service nor destination shouldn't be nil"
)
...
@@ -145,7 +145,7 @@ func (f *FakeIPVS) AddRealServer(serv *utilipvs.VirtualServer, dest *utilipvs.Re
...
@@ -145,7 +145,7 @@ func (f *FakeIPVS) AddRealServer(serv *utilipvs.VirtualServer, dest *utilipvs.Re
return
nil
return
nil
}
}
//GetRealServers is a fake implementation
//GetRealServers is a fake implementation
, it simply returns all RealServers in the cache store.
func
(
f
*
FakeIPVS
)
GetRealServers
(
serv
*
utilipvs
.
VirtualServer
)
([]
*
utilipvs
.
RealServer
,
error
)
{
func
(
f
*
FakeIPVS
)
GetRealServers
(
serv
*
utilipvs
.
VirtualServer
)
([]
*
utilipvs
.
RealServer
,
error
)
{
if
serv
==
nil
{
if
serv
==
nil
{
return
nil
,
fmt
.
Errorf
(
"Failed to get destination for nil service"
)
return
nil
,
fmt
.
Errorf
(
"Failed to get destination for nil service"
)
...
@@ -157,11 +157,28 @@ func (f *FakeIPVS) GetRealServers(serv *utilipvs.VirtualServer) ([]*utilipvs.Rea
...
@@ -157,11 +157,28 @@ func (f *FakeIPVS) GetRealServers(serv *utilipvs.VirtualServer) ([]*utilipvs.Rea
return
f
.
Destinations
[
key
],
nil
return
f
.
Destinations
[
key
],
nil
}
}
//DeleteRealServer is a fake implementation
//DeleteRealServer is a fake implementation
, it deletes the real server in the cache store.
func
(
*
FakeIPVS
)
DeleteRealServer
(
serv
*
utilipvs
.
VirtualServer
,
dest
*
utilipvs
.
RealServer
)
error
{
func
(
f
*
FakeIPVS
)
DeleteRealServer
(
serv
*
utilipvs
.
VirtualServer
,
dest
*
utilipvs
.
RealServer
)
error
{
if
serv
==
nil
||
dest
==
nil
{
if
serv
==
nil
||
dest
==
nil
{
return
fmt
.
Errorf
(
"Failed to delete destination, neither service nor destination can't be nil"
)
return
fmt
.
Errorf
(
"Failed to delete destination, neither service nor destination can't be nil"
)
}
}
key
:=
toServiceKey
(
serv
)
if
_
,
ok
:=
f
.
Services
[
key
];
!
ok
{
return
fmt
.
Errorf
(
"Failed to delete destination for service %v, service not found"
,
key
.
String
())
}
dests
:=
f
.
Destinations
[
key
]
var
i
int
for
i
=
range
dests
{
if
dests
[
i
]
.
Equal
(
dest
)
{
break
}
}
// Not Found
if
i
>=
len
(
f
.
Destinations
[
key
])
{
return
fmt
.
Errorf
(
"Failed to delete real server for service %v, real server not found"
,
key
.
String
())
}
// Delete one element
f
.
Destinations
[
key
]
=
append
(
f
.
Destinations
[
key
][
:
i
],
f
.
Destinations
[
key
][
i
+
1
:
]
...
)
return
nil
return
nil
}
}
...
...
pkg/util/ipvs/testing/fake_test.go
0 → 100644
View file @
f21cf887
/*
Copyright 2017 The Kubernetes Authors.
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
testing
import
(
"net"
"testing"
utilipvs
"k8s.io/kubernetes/pkg/util/ipvs"
)
func
TestVirtualServer
(
t
*
testing
.
T
)
{
// Initialize
fake
:=
NewFake
()
// Add a virtual server
vs1
:=
&
utilipvs
.
VirtualServer
{
Address
:
net
.
ParseIP
(
"1.2.3.4"
),
Port
:
uint16
(
80
),
Protocol
:
string
(
"TCP"
),
}
err
:=
fake
.
AddVirtualServer
(
vs1
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to add virutal server, error: %v"
,
err
)
}
// Get a specific virtual server
got1
,
err
:=
fake
.
GetVirtualServer
(
vs1
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to get virutal server, error: %v"
,
err
)
}
if
!
vs1
.
Equal
(
got1
)
{
t
.
Errorf
(
"Expect virtual server: %v, got: %v"
,
vs1
,
got1
)
}
// Add another virtual server
vs2
:=
&
utilipvs
.
VirtualServer
{
Address
:
net
.
ParseIP
(
"10::40"
),
Port
:
uint16
(
8080
),
Protocol
:
string
(
"UDP"
),
}
err
=
fake
.
AddVirtualServer
(
vs2
)
if
err
!=
nil
{
t
.
Errorf
(
"Unexpected error when add virutal server, error: %v"
,
err
)
}
// List all virtual servers
list
,
err
:=
fake
.
GetVirtualServers
()
if
err
!=
nil
{
t
.
Errorf
(
"Fail to list virutal servers, error: %v"
,
err
)
}
if
len
(
list
)
!=
2
{
t
.
Errorf
(
"Expect 2 virutal servers, got: %d"
,
len
(
list
))
}
// Delete a virtual server
err
=
fake
.
DeleteVirtualServer
(
vs1
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to delete virutal server: %v, error: %v"
,
vs1
,
err
)
}
// Check the deleted virtual server no longer exists
got
,
_
:=
fake
.
GetVirtualServer
(
vs1
)
if
got
!=
nil
{
t
.
Errorf
(
"Expect nil, got: %v"
,
got
)
}
// Flush all virtual servers
err
=
fake
.
Flush
()
if
err
!=
nil
{
t
.
Errorf
(
"Fail to flush virutal servers, error: %v"
,
err
)
}
// List all virtual servers
list
,
err
=
fake
.
GetVirtualServers
()
if
err
!=
nil
{
t
.
Errorf
(
"Fail to list virutal servers, error: %v"
,
err
)
}
if
len
(
list
)
!=
0
{
t
.
Errorf
(
"Expect 0 virutal servers, got: %d"
,
len
(
list
))
}
}
func
TestRealServer
(
t
*
testing
.
T
)
{
// Initialize
fake
:=
NewFake
()
// Add a virtual server
vs
:=
&
utilipvs
.
VirtualServer
{
Address
:
net
.
ParseIP
(
"10.20.30.40"
),
Port
:
uint16
(
80
),
Protocol
:
string
(
"TCP"
),
}
err
:=
fake
.
AddVirtualServer
(
vs
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to add virutal server, error: %v"
,
err
)
}
// Add a real server to the virtual server
rs1
:=
&
utilipvs
.
RealServer
{
Address
:
net
.
ParseIP
(
"172.16.2.1"
),
Port
:
uint16
(
8080
),
Weight
:
1
,
}
err
=
fake
.
AddRealServer
(
vs
,
rs1
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to add real server, error: %v"
,
err
)
}
// Add another real server to the virtual server
rs2
:=
&
utilipvs
.
RealServer
{
Address
:
net
.
ParseIP
(
"172.16.3.2"
),
Port
:
uint16
(
8080
),
Weight
:
2
,
}
err
=
fake
.
AddRealServer
(
vs
,
rs2
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to add real server, error: %v"
,
err
)
}
// List all real servers of the virtual server
list
,
err
:=
fake
.
GetRealServers
(
vs
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to get real servers of the virtual server, error: %v"
,
err
)
}
if
len
(
list
)
!=
2
{
t
.
Errorf
(
"Expect 2 virutal servers, got: %d"
,
len
(
list
))
}
// Delete a real server of the virtual server
err
=
fake
.
DeleteRealServer
(
vs
,
rs2
)
list
,
err
=
fake
.
GetRealServers
(
vs
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to get real servers of the virtual server, error: %v"
,
err
)
}
if
len
(
list
)
!=
1
{
t
.
Errorf
(
"Expect 1 real server, got: %d"
,
len
(
list
))
}
// Delete the virtual server
err
=
fake
.
DeleteVirtualServer
(
vs
)
if
err
!=
nil
{
t
.
Errorf
(
"Fail to delete virtual server, error: %v"
,
err
)
}
_
,
err
=
fake
.
GetRealServers
(
vs
)
if
err
==
nil
{
t
.
Errorf
(
"Expect error, got nil"
)
}
}
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