From 6a82d01b3564461ca4b888a1e55d4dc1e3e50714 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 28 Nov 2022 14:28:22 +0100 Subject: [PATCH] tests for borg transfer --upgrader=From12To20 --- .../make-testdata/test_transfer_upgrade.sh | 76 ++++++ src/borg/testsuite/archiver/repo12.tar.gz | Bin 0 -> 7499 bytes src/borg/testsuite/archiver/transfer_cmd.py | 248 ++++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100755 scripts/make-testdata/test_transfer_upgrade.sh create mode 100644 src/borg/testsuite/archiver/repo12.tar.gz diff --git a/scripts/make-testdata/test_transfer_upgrade.sh b/scripts/make-testdata/test_transfer_upgrade.sh new file mode 100755 index 00000000..42c3adb8 --- /dev/null +++ b/scripts/make-testdata/test_transfer_upgrade.sh @@ -0,0 +1,76 @@ +# this scripts uses borg 1.2 to generate test data for "borg transfer --upgrader=From12To20" +BORG=./borg-1.2.2 +# on macOS, gnu tar is available as gtar +TAR=gtar +SRC=/tmp/borgtest +ARCHIVE=`pwd`/src/borg/testsuite/archiver/repo12.tar.gz + +export BORG_REPO=/tmp/repo12 +META=$BORG_REPO/test_meta +export BORG_PASSPHRASE="waytooeasyonlyfortests" +export BORG_DELETE_I_KNOW_WHAT_I_AM_DOING=YES + +$BORG init -e repokey 2> /dev/null +mkdir $META + +# archive1 +mkdir $SRC + +pushd $SRC >/dev/null + +mkdir directory + +echo "content" > directory/no_hardlink + +echo "hardlink content" > hardlink1 +ln hardlink1 hardlink2 + +echo "symlinked content" > target +ln -s target symlink + +ln -s doesnotexist broken_symlink + +mkfifo fifo + +touch without_xattrs +touch with_xattrs +xattr -w key1 value with_xattrs +xattr -w key2 "" with_xattrs + +touch without_flags +touch with_flags +chflags nodump with_flags + +popd >/dev/null + +$BORG create ::archive1 $SRC +$BORG list ::archive1 --json-lines > $META/archive1_list.json +rm -rf $SRC + +# archive2 +mkdir $SRC + +pushd $SRC >/dev/null + +sudo mkdir root_stuff +sudo mknod root_stuff/bdev_12_34 b 12 34 +sudo mknod root_stuff/cdev_34_56 c 34 56 +sudo touch root_stuff/strange_uid_gid # no user name, no group name for this uid/gid! +sudo chown 54321:54321 root_stuff/strange_uid_gid + +popd >/dev/null + +$BORG create ::archive2 $SRC +$BORG list ::archive2 --json-lines > $META/archive2_list.json +sudo rm -rf $SRC/root_stuff +rm -rf $SRC + + +$BORG --version > $META/borg_version.txt +$BORG list :: --json > $META/repo_list.json + +pushd $BORG_REPO >/dev/null +$TAR czf $ARCHIVE . +popd >/dev/null + +$BORG delete :: 2> /dev/null diff --git a/src/borg/testsuite/archiver/repo12.tar.gz b/src/borg/testsuite/archiver/repo12.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..cd4a45b3f1eaa85b1679b61e85fcb018491103dd GIT binary patch literal 7499 zcma)fbx<4b(r#N?+>2|`phb(9phZjC;_gtOIK>i3@!*A^p+JEG#oZ;iyL-?gEv`k9 z?{~hx&N=tqnY(}OyfgdE^X~5Kv&$Tf`-rYI+WHaJVJ42rbG5sEMeQh97L4@;u7~9e>v5AS6Z(WYF4U>YU)#!3I<)jt|5Dd0+1K~ zMdbep`R}B!`AsrhOmnWnX|0WzS3T0FitP2Z+q@H2_PZCyN#{cdQDt8H=Wgy%D1m?H zixJa$y|095e~)Z(tpGF5SMk{6tBa($ga>%qe_FF?BNzUqey2ZjoF@C<>G@w)jTk+b zTK~}BqfU8$PFapbkBS`c+o%Z`za<&bw}Sk>trZr~FXCVYLP`H4wg1PD3_Xo-W|os( zT|YWOj1z_US%zUo&6OWVBH2+_(!;Kq(`N5p2D~JXL1FzzjWEUxmP)G~EeX>y5&S$& z@HS_M59t}$r}Dtq7N#W~wu*JjXH&bralt6-$NyR5Yk_TaW_8cmGYIxliTgS`>xP>t z%7(n^H~oCu8V5~85U%eN&2TjdSWKt91Z&p6TFJ4vnUaN^O#urlM&h8NiHkVu%qHQ* z;(;~UI`AF~)Sxwcr&&A!}3H&)Fr+!Xt7 z*!c)|L>!*T3cMkY)2395i>`ccsf)Y8yAzZB^93K1fF1~T#pD>qAmsJ2U3}=AsbdcnnY3?*0|2DPEAgxFgGtAEX(5X z?^9`#8#2VJ>9m8mI~%0-5=oCeC73#8-O_5rBliCUFZd_sjgpEb&~>m>J&XIQC=u;K z71_)x`;xRXT9aIXPp3m)H}cIBXT{9bCwRnHCirZjqfuu)cgoATUU4NpG#pC+3)a<< zgi*H(+z`fhD&m|G#|#?;VNIGR07>i!{I*dU0N!3_F%K->;3Eje8KulB%1+l67WTK- zjco<+eT6Hj8xXdv!3q!}6#;<7TU4J|3yV!Lky`;QGv85V4|vzbr|+$k(d89)aVpCN z)#E;`N)K@AaZ43Qij{9)ufo{0)Hrvi{X z03&)uVDFjN3bEGJy$|4;u*t@CYin+^k{l9CR8)3ck;bJD_3HN zd|tqV+osx@og41vABt#S{7(Rhgl|t8nSz)mi+LdZ79f>Q3bKU5%|GL37wch5(!Qsh zcjpQ$Qd!JeUT1`{JQxp3|qTLFk>TL0<^hBtMy#fTL(G=3_TdJ55Mgbv=~fSh!ZO=MlJiGscj-J zy=y<8xcwQG@IS!FlU^)eWq)D&a%E11=-P-PWM}T_(0XA*zrYgn6($%m4tS<9JILkO zK^EPuq-Vfq+BMNas=*Q^*vpW_Z2{wJdcx<{2QB2QVkqC{b5%R+mGHlSKG|=WhuS=O zdT&mpzO=Hs4@aF<^`{1R4p?p{QwT=UaA-3BZKtu7q#Usef1xv_`y8N=GCokklg3rV zm6gzN=#-oL;YL>=o{B|boN#8C+MTrEMO5F8$mCwnu`|a+_8>X0J2Z)ZJIdO>jA(BV zV|nKKz|0zS%DnjT2!I(slp-_fiHR3c^8FO_N>eGu0{klOB@$C|YR+)v^8kGk z^N(EpGbk=Y?w~$@xBP3c?_u>L}@kp^{GerZRZ`sRq$U z?|!;*sp0K!X#f=1?%ZACGf-Bt%deWJD2lUui|W{B{1Aq-!esz4hZXG8g^`gY-EQ%E zhA6s)adtK9uDw)kPL&v3+8gh&3MU^57l<~Q+0x)aD!Xt(yn5y2{wF~l)2l?v+M~@6 z7%BztA8PegAw%f5dBU!oTRzSWPK>z=Yk$774xHPhD7FY^UX^lZLTFXIn}yGD11X#$ z8cYr^O<}KEldjSdf4T{|prT@r%EOtOal}sFboDvU*>0nM(IeiZE%T9c_o$fjRIKw4 z`wT3At_SqZ*UOUyvN5fQ2RroXWA1`fMowwQVLxgimR?{xi4t)-wXA@%0ucG^6g((^ zwfvrD-rjhCFQg~-mj1b-SmAg(xFir9C*`dg_9juwh<9jl@MUSg5+MFyMe2;5GsxiF zivx2XRx(`xUKwnw%e-ev-p{zMVnC!2)qichDceJxtIYMtHLS3V4{{ABFcu z>WtumZ6A(-`wPTn-9l~dkIOM?>@+t>zKcl=No`X*7hDPons()_+`f<`%-URdZl9W5 zI6i48Fb5ssRhvedR|d9s7GLqgE`DOu#F*8(It>v6ZKv_UO?m zONl3;a1{gFp(rkV>7`6<2QTqVtGCj?7r=3Rk+aOr9(H;w=7*7=FspGP92u!XCOS%^ zbYbE03HrMq2_~nqmmbrT<_bca`J>b?zw0^mwX_li#t~}2&GL=t1tw424!50UM(ZW( z!zF$>STX2X-O*Jr?Os7HEc~+Zz`qa4f?`Owe zb+53Ux=G8h$9JZV2Yh{gq~sS=rX^QR(>YQHT|{dO)8p4!Bp)-lTahQa8NUr9wyje~ z-1F_XDW>{=HWHV!qP#K=rQYv>*e}C z>H%U~-YMySb+2;t&h_CVXHF+bkBD(v<;#GR1;OJ*OG_gUYlI?HpZq zXo5rGa;%;VqXTJqfo^7Tje}^aX@dFPFF8Zhd`;Y)8LxIyo&=V$>K ze}`YE`?~J#_Q9fs{#G8B(RUOq7748E?KThRh@iC-RWC# zSI$HJN@8ss27jKFgcou1K2WUYhw_Df|3GLl#uSsTJsHW=+XPa*U^-P)Xuk;3b}7~{4&y9FEOA);0!^mMgzL<237bQI)Q=5 zcHE%i%Rp$*e%rs~yAz4TD6$6cVr+8b(G~mR>$Vr-v6xI3VchS29~~|tvlAhG)JP-V zD^q#!ewC;0wU)K?95I9=j8KrKV$yz=@2G_5wS?QO&Gl3|$Rp04p(o?dH#kMNje&#X z+X}rvx^{zj;h#5+TQ+uG+my-?{SL1YtJyI&6IvYHZi2CJ&0MB%4`oYUfls!weSJNaxVO^U=_0?6e5Or#i|88;Q+ls< zQ9754vkUJ^b#N`zij^zr4Z+%M3+Rs)^%YB0+#}a~6skX7hZQ1#hKBnM-4=imza|Lgek$%5JMMP`u z<#Wo2pIPCK`hkRBo8-PxkVp^Y><3t)UmMle~6Ww z^xbHKW6kIp@?=id)B6WR`30F|)@RUuh_)5~N3gf+8I`aAelKAA>-kQs_VHT7c>BKF z-}05gVOBUGT@(3C?*JVOG#d-crJ;jobFgvO_k5=;+Rf@CLT7N`$D30k-JUJl)OmBM zZ`*zczmlS`Gakyjnfy<@frdSc!k>x^?Rx*1sw2iSj%%5}PcV-$`8RFxd4iW_%2W@! zAdI%ZA`4{)x#bUywNID1AADTXb}F&@-y_ItK{^Q8sRu|us3M+6fo*u`m7oLu%(cwk>B;`Fhl(w6cIN2TTbt8ouF44EO#Sup$dP!;ez8O zqU^H@`91I}6!3h^HP@>1lIQ}Bjk%r!WQ%u}$W1`5v*EiGLxR0sP z`G-o3`U28wax?M8$nV*Kql4G*LP~KYEd`2H^xAK@fV#~D6y&dxy#$3E_ntN!VIZF{ zo##klvPR6g^=~+)yNz0f1RWm%1Y*t?F`YNrn*XdZ@MNt(Km4 zSY~)Xy*5-c*84_QpV~Ys|4o`Uf^s`k(LQF$5iEvBTSvWBT>|}DFOU9JLy46wz4TJ_ z?a}lMV`Tf#%jxHEtE&YqZkl%iqP`0QP7;kLos}z9M^n*rtl5y%)IuY~SelaP)lLQ% zrRVt)!Kg=n!Pt@+c{(Rjo`v(fC;6`L<+LMeP9gfl@>|isdv8Q$vf|1ho#Up0x|a_h z+iiJZ5=5YwNgTo4>bS+W%yT(L9}Y1oBU=*kK1_=!nB%oyW1{jX2sZU?h8#xsIX{e- zJRE>;(%Z2CAeRTlZS~cT(pR@GqbKXHW2tvF>oU(5vMyi+b#75BiJ=4|78U8?4S{R+ zhD>=z8C)L&eFj;M8I=vrhHc>Ab-jPHDJn90j$VO~9(2r%G&R-(L$M-dB#!xJF{`3U zg3!JMw&ZIwafD=Ci2%^174H<#)@ykI%!1DyPWYbJ z+@4le#f;tJtcC2JC{ zR{Sy}pX1DJwD5-!8Zps+35K>s!7c}#Td`$$0iP_ zt7_=##u!`kZ^pE+;j!7Qq?Q69Dv|MSOBCNZdCHfL$=tE}VTSF?Py3MQ9x$uqfvU0!ZJ zlVpbJMXJ`JhuJdisFR$v`Q6rz97SX8XM>KHquQIG{H1gsmjS~hvw;&Wzi$D&Aos44 z4!mRzV@RcbxT=8Uve$3?I`I*j>D#@O;UulsEr^4<=xr_LvB2;~CYu|VeBO}?#*cOh zD$B`!rso~4;=HbGw(m7eu8BUZ_qJ{>$B|NgzDv~4pR3F$tY0Z}sfOg@(@xqJ!*yf$39LEWzN^GX<&PSO zvhV|s0*1Dd6^Fd%{VP6XwsW(c4cLs|tI&4=MytpRPf;D@wkE} zRO#n^p3y2=9zwOJ&4r5tk&SFpD{cq4ss0)Dkqv|m%*zH5%`1p;id_k@{PsPO+D}ay zR`9u@^XH}ffr01f6WecdYgeO%Wy``PQkl%0y4$?5{$eJv?Vg|ygK=T=J|KZgBXFGc z%3js{;E*y@KRe%*poApXDC16f&8w=+DvxA}uPb(XZ1#E*))QsALm_a!wmrt86P(J21PO zdyW_mqTB3MOajI2?pc3#;u(O{ZT$h5&ZRXg$Ev6_uVF zB$!NC6hoH-#pT;Y4Z)jcfkmkjWR3;D$BqKLgFU4pup+nMj0^feOF>0!B}gMt z%>MN$8L5wd&6m(=L`!o>RPZdaOBmO}sg=RVVZlXr(#=(%Wy7GJn<~R%38EsdZ z;QscPsdN@u`S6@4cJpNUquRK1=a$z;gyQ!y+OzZ00Rt77W3OOy8v6moABR7_4BEQC z|0TW~$pY$-YjzgyiBjxtHZsILs#kcmAr1O=X?us6^g-`?%8?ub@q$mxFEIJzP%;)b zjEY#42t|MB=O$`xsl_1`b8Gy(+%T%Ccu`S>3rl}0_av`aehQ^fHT{wQWhI8W!}}1tusY-<*qlq z%!&pwq5KE3KfQ(~k9sG7&W}>lI!v z-0sp|d?vn^yk?%eM~6& zz9%W=y!=%ALxTW{lee#+284s|#Fo>a==4y-CO?+%k$9{(7-N23Y-EkwO30vp$Kc)x zvvv8GAZi-Y7J%SQoxexfLzs$Vs r(xyd~PwD1U5~n2g+vY;e66wb#ZjT=Sf7nCEEwFx5cIuJb<46AkX?5ZX literal 0 HcmV?d00001 diff --git a/src/borg/testsuite/archiver/transfer_cmd.py b/src/borg/testsuite/archiver/transfer_cmd.py index c653b776..202167f1 100644 --- a/src/borg/testsuite/archiver/transfer_cmd.py +++ b/src/borg/testsuite/archiver/transfer_cmd.py @@ -1,6 +1,12 @@ +import json +import os +import stat +import tarfile +from datetime import timezone import unittest from ...constants import * # NOQA +from ...helpers.time import parse_timestamp from . import ArchiverTestCaseBase, RemoteArchiverTestCaseBase, ArchiverTestCaseBinaryBase, RK_ENCRYPTION, BORG_EXES @@ -31,11 +37,253 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.cmd(repo2, "transfer", other_repo1, "--dry-run") check_repo(repo2) + def test_transfer_upgrade(self): + # test upgrading a borg 1.2 repo to borg 2 + # testing using json is a bit problematic because parseformat (used for json dumping) + # already tweaks the values a bit for better printability (like e.g. using the empty + # string for attributes that are not present). + + # borg 1.2 repo dir contents, created by: scripts/make-testdata/test_transfer_upgrade.sh + repo12_tar = os.path.join(os.path.dirname(__file__), "repo12.tar.gz") + repo12_tzoffset = "+01:00" # timezone used to create the repo/archives/json dumps inside the tar file + + def local_to_utc(local_naive, tzoffset, tzinfo): + # local_naive was meant to be in tzoffset timezone (e.g. "+01:00"), + # but we want it non-naive in tzinfo time zone (e.g. timezone.utc). + ts = parse_timestamp(local_naive + tzoffset) + return ts.astimezone(tzinfo).isoformat(timespec="microseconds") + + dst_dir = f"{self.repository_location}1" + os.makedirs(dst_dir) + with tarfile.open(repo12_tar) as tf: + tf.extractall(dst_dir) + + other_repo1 = f"--other-repo={self.repository_location}1" + repo2 = f"--repo={self.repository_location}2" + + assert os.environ.get("BORG_PASSPHRASE") == "waytooeasyonlyfortests" + os.environ["BORG_TESTONLY_WEAKEN_KDF"] = "0" # must use the strong kdf here or it can't decrypt the key + + self.cmd(repo2, "rcreate", RK_ENCRYPTION, other_repo1) + self.cmd(repo2, "transfer", other_repo1, "--upgrader=From12To20") + self.cmd(repo2, "check") + + # check list of archives / manifest + rlist_json = self.cmd(repo2, "rlist", "--json") + got = json.loads(rlist_json) + with open(os.path.join(dst_dir, "test_meta", "repo_list.json")) as f: + expected = json.load(f) + for key in "encryption", "repository": + # some stuff obviously needs to be different, remove that! + del got[key] + del expected[key] + assert len(got["archives"]) == len(expected["archives"]) + for got_archive, expected_archive in zip(got["archives"], expected["archives"]): + del got_archive["id"] + del expected_archive["id"] + # timestamps: + # borg 1.2 transformed to local time and had microseconds = 0, no tzoffset + # borg 2 uses an utc timestamp, with microseconds and with tzoffset + for key in "start", "time": + # fix expectation: local time meant +01:00, so we convert that to utc +00:00 + expected_archive[key] = local_to_utc(expected_archive[key], repo12_tzoffset, timezone.utc) + # set microseconds to 0, so we can compare got with expected. + got_ts = parse_timestamp(got_archive[key]) + got_archive[key] = got_ts.replace(microsecond=0).isoformat(timespec="microseconds") + assert got == expected + + for archive in got["archives"]: + name = archive["name"] + # check archive contents + list_json = self.cmd(repo2, "list", "--json-lines", name) + got = [json.loads(line) for line in list_json.splitlines()] + with open(os.path.join(dst_dir, "test_meta", f"{name}_list.json")) as f: + lines = f.read() + expected = [json.loads(line) for line in lines.splitlines()] + hardlinks = {} + for g, e in zip(got, expected): + print(f"exp: {e}\ngot: {g}\n") + + # borg 1.2 parseformat uses .get("bsdflags", 0) so the json has 0 even + # if there were no bsdflags stored in the item. + # borg 2 parseformat uses .get("bsdflags"), so the json has either an int + # (if the archived item has bsdflags) or None (if the item has no bsdflags). + if e["flags"] == 0 and g["flags"] is None: + # this is expected behaviour, fix the expectation + e["flags"] = None + + # borg2 parseformat falls back to str(item.uid) if it does not have item.user, + # same for str(item.gid) and no item.group. + # so user/group are always str type, even if it is just str(uid) or str(gid). + # fix expectation (borg1 used int type for user/group in that case): + if g["user"] == str(g["uid"]) == str(e["uid"]): + e["user"] = str(e["uid"]) + if g["group"] == str(g["gid"]) == str(e["gid"]): + e["group"] = str(e["gid"]) + + for key in "mtime", "ctime", "atime": + if key in e: + e[key] = local_to_utc(e[key], repo12_tzoffset, timezone.utc) + + # borg 1 used hardlink slaves linking back to their hardlink masters. + # borg 2 uses symmetric approach: just normal items. if they are hardlinks, + # each item has normal attributes, including the chunks list, size. additionally, + # they have a hlid and same hlid means same inode / belonging to same set of hardlinks. + hardlink = bool(g.get("hlid")) # note: json has "" as hlid if there is no hlid in the item + if hardlink: + hardlinks[g["path"]] = g["hlid"] + if e["mode"].startswith("h"): + # fix expectations: borg1 signalled a hardlink slave with "h" + # borg2 treats all hardlinks symmetrically as normal files + e["mode"] = g["mode"][0] + e["mode"][1:] + # borg1 used source/linktarget to link back to hardlink master + assert e["source"] != "" + assert e["linktarget"] != "" + # fix expectations: borg2 does not use source/linktarget any more for hardlinks + e["source"] = "" + e["linktarget"] = "" + # borg 1 has size == 0 for hardlink slaves, borg 2 has the real file size + assert e["size"] == 0 + assert g["size"] >= 0 + # fix expectation for size + e["size"] = g["size"] + # Note: size == 0 for all items without a size or chunks list (like e.g. directories) + # Note: healthy == True indicates the *absence* of the additional chunks_healthy list + del g["hlid"] + assert g == e + + if name == "archive1": + # hardlinks referring to same inode have same hlid + assert hardlinks["tmp/borgtest/hardlink1"] == hardlinks["tmp/borgtest/hardlink2"] + + self.repository_path = f"{self.repository_location}2" + for archive_name in ("archive1", "archive2"): + archive, repository = self.open_archive(archive_name) + with repository: + for item in archive.iter_items(): + print(item) + + # borg1 used to store some stuff with None values + # borg2 does just not have the key if the value is not known. + item_dict = item.as_dict() + assert not any(value is None for value in item_dict.values()), f"found None value in {item_dict}" + + # with borg2, all items with chunks must have a precomputed size + assert "chunks" not in item or "size" in item and item.size >= 0 + + if item.path.endswith("directory") or item.path.endswith("borgtest"): + assert stat.S_ISDIR(item.mode) + assert item.uid > 0 + assert "hlid" not in item + elif item.path.endswith("no_hardlink") or item.path.endswith("target"): + assert stat.S_ISREG(item.mode) + assert item.uid > 0 + assert "hlid" not in item + assert len(item.chunks) > 0 + assert "bsdflags" not in item + elif item.path.endswith("hardlink1"): + assert stat.S_ISREG(item.mode) + assert item.uid > 0 + assert "hlid" in item and len(item.hlid) == 32 # 256bit + hlid1 = item.hlid + assert len(item.chunks) > 0 + chunks1 = item.chunks + size1 = item.size + assert "source" not in item + assert "hardlink_master" not in item + elif item.path.endswith("hardlink2"): + assert stat.S_ISREG(item.mode) + assert item.uid > 0 + assert "hlid" in item and len(item.hlid) == 32 # 256bit + hlid2 = item.hlid + assert len(item.chunks) > 0 + chunks2 = item.chunks + size2 = item.size + assert "source" not in item + assert "hardlink_master" not in item + elif item.path.endswith("broken_symlink"): + assert stat.S_ISLNK(item.mode) + assert item.source == "doesnotexist" + assert item.uid > 0 + assert "hlid" not in item + elif item.path.endswith("symlink"): + assert stat.S_ISLNK(item.mode) + assert item.source == "target" + assert item.uid > 0 + assert "hlid" not in item + elif item.path.endswith("fifo"): + assert stat.S_ISFIFO(item.mode) + assert item.uid > 0 + assert "hlid" not in item + elif item.path.endswith("without_xattrs"): + assert stat.S_ISREG(item.mode) + assert "xattrs" not in item + elif item.path.endswith("with_xattrs"): + assert stat.S_ISREG(item.mode) + assert "xattrs" in item + assert len(item.xattrs) == 2 + assert item.xattrs[b"key1"] == b"value" + assert item.xattrs[b"key2"] == b"" + elif item.path.endswith("without_flags"): + assert stat.S_ISREG(item.mode) + # borg1 did not store a flags value of 0 ("nothing special") + # borg2 reflects this "i do not know" by not having the k/v pair + assert "bsdflags" not in item + elif item.path.endswith("with_flags"): + assert stat.S_ISREG(item.mode) + assert "bsdflags" in item + assert item.bsdflags == stat.UF_NODUMP + elif item.path.endswith("root_stuff"): + assert stat.S_ISDIR(item.mode) + assert item.uid == 0 + assert item.gid == 0 + assert "hlid" not in item + elif item.path.endswith("cdev_34_56"): + assert stat.S_ISCHR(item.mode) + # looks like we can't use os.major/minor with data coming from another platform, + # thus we only do a rather rough check here: + assert "rdev" in item and item.rdev != 0 + assert item.uid == 0 + assert item.gid == 0 + assert item.user == "root" + assert item.group in ("root", "wheel") + assert "hlid" not in item + elif item.path.endswith("bdev_12_34"): + assert stat.S_ISBLK(item.mode) + # looks like we can't use os.major/minor with data coming from another platform, + # thus we only do a rather rough check here: + assert "rdev" in item and item.rdev != 0 + assert item.uid == 0 + assert item.gid == 0 + assert item.user == "root" + assert item.group in ("root", "wheel") + assert "hlid" not in item + elif item.path.endswith("strange_uid_gid"): + assert stat.S_ISREG(item.mode) + assert item.uid == 54321 + assert item.gid == 54321 + assert "user" not in item + assert "group" not in item + else: + raise NotImplementedError(f"test missing for {item.path}") + if archive_name == "archive1": + assert hlid1 == hlid2 + assert size1 == size2 == 16 + 1 # 16 text chars + \n + assert chunks1 == chunks2 + class RemoteArchiverTestCase(RemoteArchiverTestCaseBase, ArchiverTestCase): """run the same tests, but with a remote repository""" + @unittest.skip("only works locally") + def test_transfer_upgrade(self): + pass + @unittest.skipUnless("binary" in BORG_EXES, "no borg.exe available") class ArchiverTestCaseBinary(ArchiverTestCaseBinaryBase, ArchiverTestCase): """runs the same tests, but via the borg binary""" + + @unittest.skip("only works locally") + def test_transfer_upgrade(self): + pass