2017-04-26 17:39:16 +02:00
package zfs
import (
2020-03-27 00:57:33 +01:00
"context"
2020-04-10 21:58:28 +02:00
"strings"
2017-04-26 20:25:53 +02:00
"testing"
2019-03-22 19:41:12 +01:00
2024-10-18 19:21:17 +02:00
"github.com/zrepl/zrepl/internal/util/nodefault"
zfsprop "github.com/zrepl/zrepl/internal/zfs/property"
2020-09-07 01:20:57 +02:00
2019-03-22 19:41:12 +01:00
"github.com/stretchr/testify/assert"
2020-04-10 21:58:28 +02:00
"github.com/stretchr/testify/require"
2017-04-26 17:39:16 +02:00
)
2020-04-11 15:49:41 +02:00
// FIXME make this a platformtest
2017-04-26 17:39:16 +02:00
func TestZFSListHandlesProducesZFSErrorOnNonZeroExit ( t * testing . T ) {
2018-09-05 02:02:02 +02:00
t . SkipNow ( ) // FIXME ZFS_BINARY does not work if tests run in parallel
2017-04-26 17:39:16 +02:00
var err error
2020-03-27 00:57:33 +01:00
ctx := context . Background ( )
2017-04-26 17:39:16 +02:00
ZFS_BINARY = "./test_helpers/zfs_failer.sh"
2020-03-27 00:57:33 +01:00
_ , err = ZFSList ( ctx , [ ] string { "fictionalprop" } , "nonexistent/dataset" )
2017-04-26 17:39:16 +02:00
assert . Error ( t , err )
2018-12-11 22:01:50 +01:00
zfsError , ok := err . ( * ZFSError )
2017-04-26 17:39:16 +02:00
assert . True ( t , ok )
assert . Equal ( t , "error: this is a mock\n" , string ( zfsError . Stderr ) )
}
2017-08-05 21:15:37 +02:00
func TestDatasetPathTrimNPrefixComps ( t * testing . T ) {
p , err := NewDatasetPath ( "foo/bar/a/b" )
assert . Nil ( t , err )
p . TrimNPrefixComps ( 2 )
assert . True ( t , p . Equal ( toDatasetPath ( "a/b" ) ) )
p . TrimNPrefixComps ( ( 2 ) )
assert . True ( t , p . Empty ( ) )
p . TrimNPrefixComps ( ( 1 ) )
assert . True ( t , p . Empty ( ) , "empty trimming shouldn't do harm" )
}
2018-09-06 04:44:35 +02:00
func TestZFSPropertySource ( t * testing . T ) {
2019-03-22 19:41:12 +01:00
tcs := [ ] struct {
2020-09-07 01:20:57 +02:00
in PropertySource
2018-09-06 04:44:35 +02:00
exp [ ] string
} {
{
2020-09-07 01:20:57 +02:00
in : SourceAny ,
2018-11-16 11:12:29 +01:00
// although empty prefix matches any source
exp : [ ] string { "local" , "default" , "inherited" , "-" , "temporary" , "received" , "" } ,
2018-09-06 04:44:35 +02:00
} ,
{
2020-09-07 01:20:57 +02:00
in : SourceTemporary ,
2018-09-06 04:44:35 +02:00
exp : [ ] string { "temporary" } ,
} ,
{
2020-09-07 01:20:57 +02:00
in : SourceLocal | SourceInherited ,
2018-09-06 04:44:35 +02:00
exp : [ ] string { "local" , "inherited" } ,
} ,
}
toSet := func ( in [ ] string ) map [ string ] struct { } {
m := make ( map [ string ] struct { } , len ( in ) )
for _ , s := range in {
m [ s ] = struct { } { }
}
return m
}
for _ , tc := range tcs {
2020-09-07 01:20:57 +02:00
t . Logf ( "TEST CASE %v" , tc )
// give the parsing code some coverage
for _ , e := range tc . exp {
if e == "" {
continue // "" is the prefix that matches SourceAny
}
s , err := parsePropertySource ( e )
assert . NoError ( t , err )
t . Logf ( "s: %x %s" , s , s )
t . Logf ( "in: %x %s" , tc . in , tc . in )
assert . True ( t , s & tc . in != 0 )
}
// prefix matching
2018-09-06 04:44:35 +02:00
res := tc . in . zfsGetSourceFieldPrefixes ( )
resSet := toSet ( res )
expSet := toSet ( tc . exp )
assert . Equal ( t , expSet , resSet )
}
}
2018-10-18 15:45:58 +02:00
2019-03-21 16:08:05 +01:00
func TestDrySendRegexesHaveSameCaptureGroupCount ( t * testing . T ) {
assert . Equal ( t , sendDryRunInfoLineRegexFull . NumSubexp ( ) , sendDryRunInfoLineRegexIncremental . NumSubexp ( ) )
}
2018-10-18 15:45:58 +02:00
func TestDrySendInfo ( t * testing . T ) {
// # full send
// $ zfs send -Pnv -t 1-9baebea70-b8-789c636064000310a500c4ec50360710e72765a52697303030419460caa7a515a79680647ce0f26c48f2499525a9c5405ac3c90fabfe92fcf4d2cc140686b30972c7850efd0cd24092e704cbe725e6a632305415e5e797e803cd2ad14f743084b805001b201795
fullSend := `
resume token contents :
nvlist version : 0
object = 0x2
offset = 0x4c0000
bytes = 0x4e4228
toguid = 0x52f9c212c71e60cd
toname = zroot / test / a @ 1
full zroot / test / a @ 1 5389768
`
// # incremental send with token
// $ zfs send -nvP -t 1-ef01e717e-e0-789c636064000310a501c49c50360710a715e5e7a69766a63040c1d904b9e342877e062900d9ec48eaf293b252934b181898a0ea30e4d3d28a534b40323e70793624f9a4ca92d46220fdc1ce0fabfe927c882bc46c8a0a9f71ad3baf8124cf0996cf4bcc4d6560a82acacf2fd1079a55a29fe86004710b00d8ae1f93
incSend := `
resume token contents :
nvlist version : 0
fromguid = 0x52f9c212c71e60cd
object = 0x2
offset = 0x4c0000
bytes = 0x4e3ef0
toguid = 0xcfae0ae671723c16
toname = zroot / test / a @ 2
incremental zroot / test / a @ 1 zroot / test / a @ 2 5383936
`
2020-02-23 23:24:12 +01:00
// # incremental send with token + bookmark
2018-10-18 15:45:58 +02:00
// $ zfs send -nvP -t 1-ef01e717e-e0-789c636064000310a501c49c50360710a715e5e7a69766a63040c1d904b9e342877e062900d9ec48eaf293b252934b181898a0ea30e4d3d28a534b40323e70793624f9a4ca92d46220fdc1ce0fabfe927c882bc46c8a0a9f71ad3baf8124cf0996cf4bcc4d6560a82acacf2fd1079a55a29fe86004710b00d8ae1f93
incSendBookmark := `
resume token contents :
nvlist version : 0
fromguid = 0x52f9c212c71e60cd
object = 0x2
offset = 0x4c0000
bytes = 0x4e3ef0
toguid = 0xcfae0ae671723c16
toname = zroot / test / a @ 2
incremental zroot / test / a # 1 zroot / test / a @ 2 5383312
`
// incremental send without token
// $ sudo zfs send -nvP -i @1 zroot/test/a@2
incNoToken := `
incremental 1 zroot / test / a @ 2 10511856
size 10511856
`
// full send without token
// $ sudo zfs send -nvP zroot/test/a@3
fullNoToken := `
full zroot / test / a @ 3 10518512
size 10518512
2020-07-26 19:22:03 +02:00
`
// zero-length incremental send on ZoL 0.7.12
// (it omits the size field as well as the size line if size is 0)
// see https://github.com/zrepl/zrepl/issues/289
// fixed in https://github.com/openzfs/zfs/commit/835db58592d7d947e5818eb7281882e2a46073e0#diff-66bd524398bcd2ac70d90925ab6d8073L1245
incZeroSized_0_7_12 := `
incremental p1 with / spaces d1 @ 1 with space p1 with / spaces d1 @ 2 with space
`
fullZeroSized_0_7_12 := `
full p1 with / spaces d1 @ 2 with space
2018-10-18 15:45:58 +02:00
`
2019-03-21 16:08:05 +01:00
fullWithSpaces := "\nfull\tpool1/otherjob/ds with spaces@blaffoo\t12912\nsize\t12912\n"
fullWithSpacesInIntermediateComponent := "\nfull\tpool1/otherjob/another ds with spaces/childfs@blaffoo\t12912\nsize\t12912\n"
incrementalWithSpaces := "\nincremental\tblaffoo\tpool1/otherjob/another ds with spaces@blaffoo2\t624\nsize\t624\n"
incrementalWithSpacesInIntermediateComponent := "\nincremental\tblaffoo\tpool1/otherjob/another ds with spaces/childfs@blaffoo2\t624\nsize\t624\n"
2018-10-18 15:45:58 +02:00
type tc struct {
2019-03-22 19:41:12 +01:00
name string
in string
exp * DrySendInfo
2018-10-18 15:45:58 +02:00
expErr bool
}
tcs := [ ] tc {
{
name : "fullSend" , in : fullSend ,
exp : & DrySendInfo {
2019-03-22 19:41:12 +01:00
Type : DrySendTypeFull ,
Filesystem : "zroot/test/a" ,
From : "" ,
To : "zroot/test/a@1" ,
2018-10-18 15:45:58 +02:00
SizeEstimate : 5389768 ,
} ,
} ,
{
name : "incSend" , in : incSend ,
exp : & DrySendInfo {
Type : DrySendTypeIncremental ,
2019-03-22 19:41:12 +01:00
Filesystem : "zroot/test/a" ,
2018-10-18 15:45:58 +02:00
From : "zroot/test/a@1" ,
To : "zroot/test/a@2" ,
SizeEstimate : 5383936 ,
} ,
} ,
{
name : "incSendBookmark" , in : incSendBookmark ,
exp : & DrySendInfo {
Type : DrySendTypeIncremental ,
2019-03-22 19:41:12 +01:00
Filesystem : "zroot/test/a" ,
2018-10-18 15:45:58 +02:00
From : "zroot/test/a#1" ,
To : "zroot/test/a@2" ,
SizeEstimate : 5383312 ,
} ,
} ,
2019-03-22 19:41:12 +01:00
{
2018-10-18 15:45:58 +02:00
name : "incNoToken" , in : incNoToken ,
2018-10-19 17:13:32 +02:00
exp : & DrySendInfo {
2019-03-22 19:41:12 +01:00
Type : DrySendTypeIncremental ,
2018-10-19 17:13:32 +02:00
Filesystem : "zroot/test/a" ,
// as can be seen in the string incNoToken,
// we cannot infer whether the incremental source is a snapshot or bookmark
From : "1" , // yes, this is actually correct on ZoL 0.7.11
To : "zroot/test/a@2" ,
SizeEstimate : 10511856 ,
} ,
2018-10-18 15:45:58 +02:00
} ,
{
name : "fullNoToken" , in : fullNoToken ,
exp : & DrySendInfo {
2019-03-22 19:41:12 +01:00
Type : DrySendTypeFull ,
Filesystem : "zroot/test/a" ,
From : "" ,
To : "zroot/test/a@3" ,
2018-10-18 15:45:58 +02:00
SizeEstimate : 10518512 ,
} ,
} ,
2019-03-21 16:08:05 +01:00
{
name : "fullWithSpaces" , in : fullWithSpaces ,
exp : & DrySendInfo {
Type : DrySendTypeFull ,
Filesystem : "pool1/otherjob/ds with spaces" ,
From : "" ,
To : "pool1/otherjob/ds with spaces@blaffoo" ,
SizeEstimate : 12912 ,
} ,
} ,
{
name : "fullWithSpacesInIntermediateComponent" , in : fullWithSpacesInIntermediateComponent ,
exp : & DrySendInfo {
Type : DrySendTypeFull ,
Filesystem : "pool1/otherjob/another ds with spaces/childfs" ,
From : "" ,
To : "pool1/otherjob/another ds with spaces/childfs@blaffoo" ,
SizeEstimate : 12912 ,
} ,
} ,
{
name : "incrementalWithSpaces" , in : incrementalWithSpaces ,
exp : & DrySendInfo {
Type : DrySendTypeIncremental ,
Filesystem : "pool1/otherjob/another ds with spaces" ,
From : "blaffoo" ,
To : "pool1/otherjob/another ds with spaces@blaffoo2" ,
SizeEstimate : 624 ,
} ,
} ,
{
name : "incrementalWithSpacesInIntermediateComponent" , in : incrementalWithSpacesInIntermediateComponent ,
exp : & DrySendInfo {
Type : DrySendTypeIncremental ,
Filesystem : "pool1/otherjob/another ds with spaces/childfs" ,
From : "blaffoo" ,
To : "pool1/otherjob/another ds with spaces/childfs@blaffoo2" ,
SizeEstimate : 624 ,
} ,
} ,
2020-07-26 19:22:03 +02:00
{
name : "incrementalZeroSizedOpenZFS_pre0.7.12" , in : incZeroSized_0_7_12 ,
exp : & DrySendInfo {
Type : DrySendTypeIncremental ,
Filesystem : "p1 with/ spaces d1" ,
From : "p1 with/ spaces d1@1 with space" ,
To : "p1 with/ spaces d1@2 with space" ,
SizeEstimate : 0 ,
} ,
} ,
{
name : "fullZeroSizedOpenZFS_pre0.7.12" , in : fullZeroSized_0_7_12 ,
exp : & DrySendInfo {
Type : DrySendTypeFull ,
Filesystem : "p1 with/ spaces d1" ,
To : "p1 with/ spaces d1@2 with space" ,
SizeEstimate : 0 ,
} ,
} ,
2018-10-18 15:45:58 +02:00
}
for _ , tc := range tcs {
t . Run ( tc . name , func ( t * testing . T ) {
in := tc . in [ 1 : ] // strip first newline
var si DrySendInfo
err := si . unmarshalZFSOutput ( [ ] byte ( in ) )
2020-07-26 19:22:03 +02:00
t . Logf ( "%#v" , & si )
t . Logf ( "err=%T %s" , err , err )
2018-10-18 15:45:58 +02:00
if tc . expErr {
assert . Error ( t , err )
}
if tc . exp != nil {
assert . Equal ( t , tc . exp , & si )
}
} )
}
}
2020-04-10 21:58:28 +02:00
func TestTryRecvDestroyOrOverwriteEncryptedErr ( t * testing . T ) {
msg := "cannot receive new filesystem stream: zfs receive -F cannot be used to destroy an encrypted filesystem or overwrite an unencrypted one with an encrypted one\n"
assert . GreaterOrEqual ( t , RecvStderrBufSiz , len ( msg ) )
err := tryRecvDestroyOrOverwriteEncryptedErr ( [ ] byte ( msg ) )
require . NotNil ( t , err )
assert . EqualError ( t , err , strings . TrimSpace ( msg ) )
}
2020-09-07 01:20:57 +02:00
func TestZFSSendArgsBuildSendFlags ( t * testing . T ) {
type args = ZFSSendFlags
type SendTest struct {
conf args
exactMatch bool
flagsInclude [ ] string
flagsExclude [ ] string
}
withEncrypted := func ( justFlags ZFSSendFlags ) ZFSSendFlags {
if justFlags . Encrypted == nil {
justFlags . Encrypted = & nodefault . Bool { B : false }
}
return justFlags
}
var sendTests = map [ string ] SendTest {
"Empty Args" : {
conf : withEncrypted ( args { } ) ,
flagsInclude : [ ] string { } ,
flagsExclude : [ ] string { "-w" , "-p" , "-b" } ,
} ,
"Raw" : {
conf : withEncrypted ( args { Raw : true } ) ,
flagsInclude : [ ] string { "-w" } ,
flagsExclude : [ ] string { } ,
} ,
"Encrypted" : {
conf : withEncrypted ( args { Encrypted : & nodefault . Bool { B : true } } ) ,
flagsInclude : [ ] string { "-w" } ,
flagsExclude : [ ] string { } ,
} ,
"Unencrypted_And_Raw" : {
conf : withEncrypted ( args { Encrypted : & nodefault . Bool { B : false } , Raw : true } ) ,
flagsInclude : [ ] string { "-w" } ,
flagsExclude : [ ] string { } ,
} ,
"Encrypted_And_Raw" : {
conf : withEncrypted ( args { Encrypted : & nodefault . Bool { B : true } , Raw : true } ) ,
flagsInclude : [ ] string { "-w" } ,
flagsExclude : [ ] string { } ,
} ,
"Send properties" : {
conf : withEncrypted ( args { Properties : true } ) ,
flagsInclude : [ ] string { "-p" } ,
flagsExclude : [ ] string { } ,
} ,
"Send backup properties" : {
conf : withEncrypted ( args { BackupProperties : true } ) ,
flagsInclude : [ ] string { "-b" } ,
flagsExclude : [ ] string { } ,
} ,
"Send -b and -p" : {
conf : withEncrypted ( args { Properties : true , BackupProperties : true } ) ,
flagsInclude : [ ] string { "-p" , "-b" } ,
flagsExclude : [ ] string { } ,
} ,
"Send resume state" : {
conf : withEncrypted ( args { Saved : true } ) ,
flagsInclude : [ ] string { "-S" } ,
} ,
"Resume token wins if not empty" : {
conf : withEncrypted ( args { ResumeToken : "$theresumetoken$" , Compressed : true } ) ,
flagsInclude : [ ] string { "-t" , "$theresumetoken$" } ,
exactMatch : true ,
} ,
}
for testName , test := range sendTests {
t . Run ( testName , func ( t * testing . T ) {
flags := test . conf . buildSendFlagsUnchecked ( )
assert . GreaterOrEqual ( t , len ( flags ) , len ( test . flagsInclude ) )
assert . Subset ( t , flags , test . flagsInclude )
if test . exactMatch {
assert . Equal ( t , flags , test . flagsInclude )
}
for flag := range flags {
assert . NotContains ( t , test . flagsExclude , flag )
}
} )
}
}
func TestZFSCommonRecvArgsBuild ( t * testing . T ) {
type RecvTest struct {
conf RecvOptions
flagsInclude [ ] string
flagsExclude [ ] string
}
var recvTests = map [ string ] RecvTest {
"Empty Args" : {
conf : RecvOptions { } ,
flagsInclude : [ ] string { } ,
flagsExclude : [ ] string { "-x" , "-o" , "-F" , "-s" } ,
} ,
"ForceRollback" : {
conf : RecvOptions { RollbackAndForceRecv : true } ,
flagsInclude : [ ] string { "-F" } ,
flagsExclude : [ ] string { "-x" , "-o" , "-s" } ,
} ,
"PartialSend" : {
conf : RecvOptions { SavePartialRecvState : true } ,
flagsInclude : [ ] string { "-s" } ,
flagsExclude : [ ] string { "-x" , "-o" , "-F" } ,
} ,
"Override properties" : {
conf : RecvOptions { OverrideProperties : map [ zfsprop . Property ] string { zfsprop . Property ( "abc" ) : "123" } } ,
flagsInclude : [ ] string { "-o" , "abc=123" } ,
flagsExclude : [ ] string { "-x" , "-F" , "-s" } ,
} ,
"Exclude/inherit properties" : {
conf : RecvOptions { InheritProperties : [ ] zfsprop . Property { "abc" , "123" } } ,
flagsInclude : [ ] string { "-x" , "abc" , "123" } , flagsExclude : [ ] string { "-o" , "-F" , "-s" } ,
} ,
}
for testName , test := range recvTests {
t . Run ( testName , func ( t * testing . T ) {
flags := test . conf . buildRecvFlags ( )
assert . Subset ( t , flags , test . flagsInclude )
for flag := range flags {
assert . NotContains ( t , test . flagsExclude , flag )
}
} )
}
}