From 1ba438d4c77130f148dadf1fe3b62eba8a85873f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:56:54 +0000 Subject: [PATCH] Add tests for plain .Z files (not tar-wrapped) - Create test .Z file (large_test.txt.Z) using compress tool - Add tests for direct LzwReader usage with plain .Z files - Add tests for ReaderFactory detection of plain .Z files - Improve filename derivation to unwrap SharpCompressStream - Verify decompression works correctly for non-tar .Z files - All 15 tests now passing Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- src/SharpCompress/Common/Lzw/LzwFilePart.cs | 9 +++- src/SharpCompress/packages.lock.json | 12 ++--- .../Lzw/LzwReaderAsyncTests.cs | 28 ++++++++++++ .../SharpCompress.Test/Lzw/LzwReaderTests.cs | 42 ++++++++++++++++++ tests/TestArchives/Archives/large_test.txt.Z | Bin 0 -> 5348 bytes 5 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 tests/TestArchives/Archives/large_test.txt.Z diff --git a/src/SharpCompress/Common/Lzw/LzwFilePart.cs b/src/SharpCompress/Common/Lzw/LzwFilePart.cs index 707d16cd..087ab3aa 100644 --- a/src/SharpCompress/Common/Lzw/LzwFilePart.cs +++ b/src/SharpCompress/Common/Lzw/LzwFilePart.cs @@ -36,8 +36,15 @@ internal sealed partial class LzwFilePart : FilePart private static string? DeriveFileName(Stream stream) { + // Unwrap SharpCompressStream to get to the underlying FileStream + var unwrappedStream = stream; + if (stream is SharpCompress.IO.IStreamStack streamStack) + { + unwrappedStream = streamStack.BaseStream(); + } + // Try to derive filename from FileStream - if (stream is FileStream fileStream && !string.IsNullOrEmpty(fileStream.Name)) + if (unwrappedStream is FileStream fileStream && !string.IsNullOrEmpty(fileStream.Name)) { var fileName = Path.GetFileName(fileStream.Name); // Strip .Z extension if present diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 29e7a1bd..27e9e496 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -216,9 +216,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -264,9 +264,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.22, )", - "resolved": "8.0.22", - "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" + "requested": "[8.0.23, )", + "resolved": "8.0.23", + "contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs b/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs index b9083138..6ecc6047 100644 --- a/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Lzw/LzwReaderAsyncTests.cs @@ -1,4 +1,7 @@ +using System.IO; using SharpCompress.Common; +using SharpCompress.Readers; +using SharpCompress.Readers.Lzw; using Xunit; namespace SharpCompress.Test.Lzw; @@ -12,4 +15,29 @@ public class LzwReaderAsyncTests : ReaderTests { await ReadAsync("Tar.tar.Z", CompressionType.Lzw); } + + [Fact] + public async System.Threading.Tasks.Task Lzw_Reader_Plain_Z_File_Async() + { + // Test async reading of a plain .Z file (not tar-wrapped) using LzwReader directly + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "large_test.txt.Z")); + using var reader = LzwReader.OpenReader(stream); + + Assert.Equal(ArchiveType.Lzw, reader.ArchiveType); + Assert.True(reader.MoveToNextEntry()); + + var entry = reader.Entry; + Assert.NotNull(entry); + Assert.Equal(CompressionType.Lzw, entry.CompressionType); + + // When opened as FileStream, key should be derived from filename + Assert.Equal("large_test.txt", entry.Key); + + // Decompress asynchronously + using var entryStream = reader.OpenEntryStream(); + using var ms = new MemoryStream(); + await entryStream.CopyToAsync(ms); + + Assert.Equal(22300, ms.Length); + } } diff --git a/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs b/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs index 4c7f2d6d..f75fd666 100644 --- a/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs +++ b/tests/SharpCompress.Test/Lzw/LzwReaderTests.cs @@ -46,4 +46,46 @@ public class LzwReaderTests : ReaderTests Assert.NotNull(reader.Entry); Assert.Equal(CompressionType.Lzw, reader.Entry.CompressionType); } + + [Fact] + public void Lzw_Reader_Plain_Z_File() + { + // Test with a plain .Z file (not tar-wrapped) using LzwReader directly + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "large_test.txt.Z")); + using var reader = LzwReader.OpenReader(stream); + + Assert.True(reader.MoveToNextEntry()); + var entry = reader.Entry; + Assert.NotNull(entry); + Assert.Equal(CompressionType.Lzw, entry.CompressionType); + + // Entry key should be "large_test.txt" (stripped .Z extension) when opened via FileStream + Assert.Equal("large_test.txt", entry.Key); + + // Decompress and verify content + using var entryStream = reader.OpenEntryStream(); + using var ms = new MemoryStream(); + entryStream.CopyTo(ms); + var decompressed = System.Text.Encoding.UTF8.GetString(ms.ToArray()); + + Assert.Equal(22300, ms.Length); + Assert.Contains("This is a test file for LZW compression testing", decompressed); + } + + [Fact] + public void Lzw_Reader_Factory_Detects_Plain_Z_File() + { + // Test that ReaderFactory correctly identifies a plain .Z file (not tar-wrapped) + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "large_test.txt.Z")); + using var reader = ReaderFactory.OpenReader(stream); + + // Should detect as Lzw archive (not Tar) + Assert.Equal(ArchiveType.Lzw, reader.ArchiveType); + Assert.True(reader.MoveToNextEntry()); + Assert.NotNull(reader.Entry); + Assert.Equal(CompressionType.Lzw, reader.Entry.CompressionType); + + // When opened via ReaderFactory with a non-FileStream, key defaults to "data" + Assert.NotNull(reader.Entry.Key); + } } diff --git a/tests/TestArchives/Archives/large_test.txt.Z b/tests/TestArchives/Archives/large_test.txt.Z new file mode 100644 index 0000000000000000000000000000000000000000..64ae2af12404f804db4d2da8bbaf7be327311dd5 GIT binary patch literal 5348 zcmb22J0ax4l9|j&3LORtFQ&|NW^b8kl5@(UMwy*OSU23Er}8pe-<;OsOM40@7bwm( z$vd|rKp^1J;SPm6Q#7616y};M9PvmIb~`88nR;i>%3?NOr#XqoF02%8VTCwMZaKaiM`?$G19dy{I1Md_uU$jQOyW?QSA-E)xH z$@l&yg?l$NgAL!FGpJhdFLWh?SJ0*01znN6k8+~gGt-)XtX9|HFw1n{E~{O(j!9*? z2V|0^iI!<1cV9v;3A< zxXIG_Nl6}J!E&+-7tY8MZB&!W%vusSw=DI+#!G@1IxpUmUM#4xP>$QILZofY#6oxf zv=TJ|M>fSJ0?sE6vMh9!a6KyKp)}FtaoC>9ZjC{vu6}s6jktsgss2=Nj zC9?gltVS98)rCtY8pc(o&JlO3TEFqRT+OG8>TR`49;zR2TzmE5d>6)Bh6|WjH! zv}3vE6dXx>v(jb3B$X`L%Yvy{Ww%%E%B@&8iM^@v1?%p(>h}w$WY_MmoqoUWARo6v z$gz8l3mP+wU$1EvcCGllMLcRXLwjb$3bu~y4vDDk_l0^U@6g)xVv(cawc8gals=Zw zpJ{XG>*Kwq&!biLv;N+la==e2Z^IPM^cb~i{P~)N(-R|Qo)vQMcGaI%QhVoFTtS~q z#L0}CokjC>oSzw`8gN?~@4NJDkH-1+KW(HhZ0G&kwlp<;)|MG3YJa&E`?<55t_)gV zlC?^+Qa$(DhErazuW#>?oxN_)tiRf~{{9Vrv+<IK_*owC2TRICnbTX%JP4r^X? zi*evV0e8J()|SFoMKSyGSoAN4o^w|_IA?-_(~??QP4(@JGtyYTEopM)Obl6cXR6dR z)sED(Qju)g8C}hLZ{+rr$ptga{?xSF;%{ns^UJ#@KP)k_wO!_aggN94TkD?Pn)&H* z#aBKniQ3-vS)Xy$`PQ-x+f6JB-&@}6jw(IkzLh8PZO!^yQEPTx(}-@>wR^98yLN_= z{XdS=bvcLS?+9$!f9U7?ERjWGT^ujW4{-KH7juWV9b3Ai*ztpcP>T#}af)+Qte)zR zraRB%;~j-kGEm7V#L0ZjZ)K!%4DO3PgJe2w&MKgrzw5%)HfBI zlPZP*GZT}ZO-h%VyRxlPwoqxt``WR3E-}e~alN7rlaso!ZwQC5Rl9 z_x%t%rDo|AAG=KdWgKSzGqGuxJR#ljk~g8A%k)c+s&dS1k|V8P3# zC620{7f!a8Bv0p=v_vjzNr%?S$s)X}FM}$=uPii5Qcj%fzLYbl_n?ul>eGjw*(Y~T z{kN{;V0Grlrb?SxOdmRrAIZ#FcEV?&<>uw?zh8Ti!oK`vA6LF^ zX12WRezo}A$DFc|+kba(`R`miHP3w2a)qzQwmW?)F8F67<8c3#N7JdPu_bFgShaTs zRGXGG8{AwH|KIfD1x~TnB|19X!P8G2DZ5hIbMysoa;EjkE+0P!6G?Bw*Owk1Ea{y4 zmB&z&_2|6bvYC#D&t_IncD;IR`Ye~X_e{NA-EPh*T$xewN^0RPx0Rp7>R#;8`nze@ zwzd0wBja+f75-mwr+V7fooqHKOgFV+x0`)xycR3oyGh&fXVj#HRi&KlU$0(2=P6RM zz+|C@ce?Z8ypXA)vlWl+s8;*+s$kmWqw95by7F*c9=W1?7%Z)d31nTao!P0X(2pM7;+Ztl;T=-mH5dy_w}4Q-UpTf2?-qUJp5 z?yGN;oQ_E}en@-Jd-3Xyo%f=5bMAfJzgIft>`!S=ce#a?Q+*14Zrin2xOwZ8Sk12! zqbl3CyJgnpS4Qfmi`|fUt~1j<@7V<9;4Y4Jk4{RjDZ0jYb*o!zW$Rko;x$)e=E$;@ zoMYF0^q}ka^Cx~=E=bDCxf_SiZ*#F)dRr@>-DvNE8?{yaE8py><+j`t&c1LZb6jAk z_@5H?`b~#YPkcCb?(`c0cWd3^wL#t6zg$n`+h)+&tC768^2rk44PWNH%vn&@^|WEV zanSZkx7%3~AGdxg*uGlJevMSsomkWFPcGkmQMzB^^Lf`N&$91S?l80cVs^;<!`S6{bU<=!5*{`kGCiQ5b0r{-@7vldK`-{Drc%gjamd3M6KLPOJd9 z^SoK%7E`a)&G=SltyXpWX~CE00e{QtSFsoTQZM*Z9_ah6K4E#pMz^y0VGWi`sw0;* z>^jg_n-(HomMb~UPVsA1EiH3PvV1ZEeZsur+ank~ zs+m2iR8q5dFK_*IwM^oB*+KF0L)ToAD}pnp)l_Y7ta)B@^K-c(M|87#vDyu-_VD&y zOKZEQw?FvSR3lO8_`Tgsq9bK$^UUxLt|N6FFX|SEw|u*eJM||G(_>~fkjAo5T!yC(|2TNuYZTgY6g(LCe^R~M%<-(CN$s^^? zjF$PETh?AL^|$B?{1Nvoqgwn+0b56_@bsXiC$jlg6vp2uTocis@}sNrcv|?C9L?+f z7s|V@OzSR;sMr%R;Wk@O#gAO>m&LrAl_w-7K2`5{d#z`3TE_*C*e`4?GB5S6S=1|J zw%k798Tq|e{bs%9O{XU(CT%V6U0BiZ`b4$q%?8Qkk*hQslTJ)t*HL4?ve9v78{f9J zZ8zF>cr@+m$laSUq0A!m-nQbyJKEdRdg^v$oRo0rpEmWSdGeygC7-`lUU%(HdO7h$ zM{nryaOKF(HRW-BD>?&CbUu}szM!Hk!fbN(@zjdWE&+}MhTycYoU7x12afbKbn0&5}Q-N}udl7TLN& za?+A(y{apxt()2TDs(>Q_0De9?By%xZ$CMI=gaCptTlUAPM-I4`th>HG>PuxC;NG# zrkwe?pg>~bg_*fGzR!?9R@*Xdp@GHJ<2M%iq*lHWoA-WO-P4^VT^aeZE9YsQns@8P zyo{Ok23qsiTQ1JqIVs6~_Rq}OLdO@29&fdO)taPMzTb1fp~`-Vjs+PVa|(~O=}n*E zZ#6+(W3F%H+=(}9Z>(Hc`(r}r%Y~v$ zVy)=!rMGU)^fZr6;aay?Yu!?>b&A=gQ#j_U?OwNbx79w*1<(?Nnd+%~p$_ z%U4@0*?((t!_Lj^tClcp=Qf%*oziMQleJ~ysSOuawci(SKKeEE1nbtgs=8q9MWNZ5 z_oMP7UvG@&-n4wzvXx!y8br6vu-L{Eylr#&;t-E@R$QBNUeDjXbG7Bo)kWQ#OJ1*U z%$&oxx=nle3dhsi*EYN?g`$_TEF?cU9u-W$75_k?IKlUhAdrhAdRcF+Wg ztY@=!s_fqR`})rH-*;+7FWzjqS}ST-cJ}lYuebfo+PpS;K}*$=!>bn9MejadwL5>t zZXvCuqOYc0Te)TGsV&nwSH|2*Qn|^_uhZ#O9WpQT|OHFd|yxD<5IolRh_ld39CUIx} zF3)|Y+Dnd@Z&$2YkiMh5;`in%?OMa{b9zor*}J>eP-d+$Pu*eeiO0H|MRu-LuIhNE zxr)taCx_0F*wwREMQyq+-IvR``cC!yjNeBLJ=ZU{J}MovFU+SuB4&wF&We~jHQrgf zHDxBKS{&o5o?vjNX>#O_sH&O5x0mM0EV8bdcshI0+1c|Pd%~w{Z@Th&5#OF^8s6Ky zWp)L>II^Vr;9`xVcE9=@cI_&xKC1BML{!g`&CxU7ac`L_b1b0ivV(7!Sv{ZJ9&=ozO%UI)#}Su$t2X70aoX5qp&`{%4Wd|T(hgx6;uW*xsi zu@twxyb1r`N#W}B!3+_4R^XFpG%F~f2PJdpr zdOy$h8qF1!(c6`4w*UQe>HmoppQM-eO756lbK&6X%U6B&nEzSGyY{Su?(uN#ouzj& zMem-9>%O>p*A;<3t2mCI^X0kpne}|I?ObMegYvrDRuuc^5*Bl<|f?c=p`H~HnB)|I^4(mQ)c z)r~!zXCiyg_x?Tcd(M*hxo6^eCY*V*!h7b)kgCfWe>Y^Uz2JZQn4RpcJEzVr={>cq z_CWdCTkFo;@_Kt~9oOypzgITvu5ZoR^{D&Amoo>eUmlG5yK7(WnK$0M>z1EM(7Alf zcA@FqJ2QFroH={*Tg8A7t=xNjW6zPBtB-7ad-3txO=5PJw*Q^Kv-j$ov$xr}9=+Cnbi(HTVcly} zeXphPoITaM@bBM8|L-K9-t*eO1f5pAwqwQ0bxThzruAQ56|BB|0be|nJZ*I9ZXXf$W$4<`PcsJ&j zrQI~^eGhKmeQ@{hgUvF}h5Vj~_?-yPIPv`MX{mMhepPMOTDv)x_vTygrv|b21*-0U zsowJK_J$0fng3$fo|k=K95<0A@3Csvdv#ZoXZRJo-DL`{_f8trP}A; zx^AyJ{Y3U&SKYt$o1|Z;t(nuJw`ID|WwYf^f9oFJ^X}1?`+O{w6*WikJ@);bKmWL zck&9?sr~=n-IRIXU;Vhm=gNtF^D6ef`E~Eji#bP@UAcJ4Zr2sP4^>_tD)&8HUAK?( z{i_LQK5Vpm!Jq&1>A&qAeOtcYe)Rg^$v?X<2x&ez6!+LM_t{szbD8V%Z|->Mx%c|L zzb`N7RWEwCw>0k6v)+d*{5GraJt2SRRm|Ma2kw5@BzLW~?rFQl<0o4^7~|y_i4kP_vhAqnk{kS1J{S<+~=*m&qHj!t(kqvy#Cw%+A|OD zE%5caX}^EB!&+wb@Kou~YpfV+}pjeX9D96xpW?@dH_H8 B?C$^o literal 0 HcmV?d00001