minitar 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +285 -0
- data/CONTRIBUTING.md +273 -0
- data/CONTRIBUTORS.md +27 -0
- data/LICENCE.md +39 -0
- data/Manifest.txt +29 -6
- data/README.md +70 -0
- data/Rakefile +74 -19
- data/SECURITY.md +64 -0
- data/docs/ruby.txt +3 -3
- data/lib/minitar/input.rb +69 -56
- data/lib/minitar/output.rb +34 -22
- data/lib/minitar/pax_header.rb +111 -0
- data/lib/minitar/posix_header.rb +96 -57
- data/lib/minitar/reader.rb +65 -70
- data/lib/minitar/version.rb +5 -0
- data/lib/minitar/writer.rb +50 -88
- data/lib/minitar.rb +60 -64
- data/licenses/bsdl.txt +20 -0
- data/licenses/dco.txt +34 -0
- data/licenses/ruby.txt +52 -0
- data/test/fixtures/issue_46.tar.gz +0 -0
- data/test/fixtures/issue_62.tar.gz +0 -0
- data/test/fixtures/tar_input.tgz +0 -0
- data/test/fixtures/test_input_non_strict_octal.tgz +0 -0
- data/test/fixtures/test_input_relative.tgz +0 -0
- data/test/fixtures/test_input_space_octal.tgz +0 -0
- data/test/fixtures/test_minitar.tar.gz +0 -0
- data/test/minitest_helper.rb +12 -1
- data/test/support/minitar_test_helpers/fixtures.rb +38 -0
- data/test/support/minitar_test_helpers/header.rb +130 -0
- data/test/support/minitar_test_helpers/tarball.rb +324 -0
- data/test/support/minitar_test_helpers.rb +36 -0
- data/test/test_filename_boundary_conditions.rb +74 -0
- data/test/test_gnu_tar_compatibility.rb +92 -0
- data/test/test_integration_pack_unpack_cycle.rb +38 -0
- data/test/test_issue_46.rb +5 -23
- data/test/test_issue_62.rb +50 -0
- data/test/test_minitar.rb +168 -39
- data/test/test_pax_header.rb +104 -0
- data/test/test_pax_support.rb +66 -0
- data/test/test_tar_header.rb +289 -75
- data/test/test_tar_input.rb +14 -61
- data/test/test_tar_output.rb +7 -9
- data/test/test_tar_reader.rb +17 -18
- data/test/test_tar_writer.rb +105 -126
- metadata +95 -89
- data/Contributing.md +0 -94
- data/History.md +0 -236
- data/Licence.md +0 -15
- data/README.rdoc +0 -92
- data/test/support/tar_test_helpers.rb +0 -134
- /data/{Code-of-Conduct.md → CODE_OF_CONDUCT.md} +0 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitest_helper"
|
4
|
+
|
5
|
+
class TestIntegrationPackUnpackCycle < Minitest::Test
|
6
|
+
def test_comprehensive
|
7
|
+
files = MIXED_FILENAME_SCENARIOS.merge(
|
8
|
+
VERY_LONG_FILENAME_SCENARIOS,
|
9
|
+
BOUNDARY_SCENARIOS,
|
10
|
+
{
|
11
|
+
"empty_dir" => nil,
|
12
|
+
"nested/empty" => nil,
|
13
|
+
"long_dir_#{"i" * 120}" => nil,
|
14
|
+
"root_file.txt" => "root content",
|
15
|
+
"level1/file1.txt" => "level1 content",
|
16
|
+
"level1/level2/file2.txt" => "level2 content",
|
17
|
+
"level1/level2/level3/#{"deep" * 30}.txt" => "deep nested with long name",
|
18
|
+
"#{"long_dir" * 20}/file_in_long_dir.txt" => "file in long directory name",
|
19
|
+
"mixed/#{"long_subdir" * 15}/#{"long_file" * 25}.txt" => "long dir and file names"
|
20
|
+
}
|
21
|
+
)
|
22
|
+
|
23
|
+
workspace with_files: files do |ws|
|
24
|
+
minitar_pack_in_workspace
|
25
|
+
|
26
|
+
assert ws.tarball.file?, "Tarball does not exist"
|
27
|
+
assert ws.tarball.size > 0, "Tarball should not be empty"
|
28
|
+
|
29
|
+
minitar_unpack_in_workspace
|
30
|
+
|
31
|
+
assert_files_extracted_in_workspace
|
32
|
+
refute_file_path_duplication_in_workspace
|
33
|
+
|
34
|
+
assert_extracted_files_match_source_files_in_workspace
|
35
|
+
assert_file_modes_match_in_workspace
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/test/test_issue_46.rb
CHANGED
@@ -1,44 +1,26 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "minitar"
|
4
3
|
require "minitest_helper"
|
5
|
-
require "base64"
|
6
|
-
require "zlib"
|
7
4
|
|
8
5
|
class TestIssue46 < Minitest::Test
|
9
|
-
SUPERLONG_TGZ = Base64.decode64(<<~EOS).freeze
|
10
|
-
H4sIAK1+smYAA+3WQQ6CMBAF0K49BScAprYd3XkALoECSiQlQYzXt0IkSKLGBdXE
|
11
|
-
/zbtNF000PkQRmG0SWq7T0p7FPOIHaNUNzrTkWI5zPt1IiYtgmSm8zw4n9q0CQLR
|
12
|
-
1HX7at/lkOeVjwP5FZNcKm14tU63uyyPUP91/e3rCJ75uF/j/Gej+6yXw/fArbnM
|
13
|
-
Z2ZlDKlb/ktNrEQQ+3gA9/xP3aS0z/e5bUXh40B+/Vj+oJ63Xkzff26zoqzmzf13
|
14
|
-
/d/98437n0izQf8DAAAAAAAAAAAAAAAAAHziCqQuXDYAKAAA
|
15
|
-
EOS
|
16
|
-
|
17
|
-
FILETIMES = Time.utc(2004).to_i
|
18
|
-
|
19
|
-
superlong_name = (["0123456789abcde"] * 33).join("/")
|
20
|
-
|
21
6
|
SUPERLONG_CONTENTS = {
|
22
|
-
|
7
|
+
["0123456789abcde"].then { _1 * 33 }.join("/") => {size: 496, mode: 0o644},
|
23
8
|
"endfile" => {size: 0, mode: 0o644}
|
24
9
|
}
|
25
10
|
|
26
11
|
def test_each_works
|
27
|
-
|
28
|
-
|
29
|
-
Minitar::Input.open(reader) do |stream|
|
12
|
+
Minitar::Input.open(open_fixture("issue_46")) do |stream|
|
30
13
|
outer = 0
|
31
14
|
stream.each.with_index do |entry, i|
|
32
15
|
assert_kind_of Minitar::Reader::EntryStream, entry
|
33
16
|
assert SUPERLONG_CONTENTS.key?(entry.name), "File #{entry.name} not defined"
|
34
17
|
|
35
18
|
assert_equal SUPERLONG_CONTENTS[entry.name][:size],
|
36
|
-
entry.size,
|
37
|
-
"File sizes sizes do not match: #{entry.name}"
|
19
|
+
entry.size, "File sizes sizes do not match: #{entry.name}"
|
38
20
|
|
39
21
|
assert_modes_equal(SUPERLONG_CONTENTS[entry.name][:mode],
|
40
22
|
entry.mode, entry.name)
|
41
|
-
assert_equal
|
23
|
+
assert_equal TIME_2004, entry.mtime, "entry.mtime"
|
42
24
|
|
43
25
|
outer += 1
|
44
26
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitest_helper"
|
4
|
+
|
5
|
+
class TestIssue62 < Minitest::Test
|
6
|
+
# These are representative filenames from issue #62 which were extracted incorrectly.
|
7
|
+
FILENAMES = [
|
8
|
+
"hpg5lfg/1j/973e4t/hqc/djcrcb1l49ardcthyl5u80dcgmo03cp5mh938wr38dka7us1ja4i3dfrp3ahg4q2ooet6avyw45nqpzrcxfzdemvzj07oftcghtkl5bdc.gz",
|
9
|
+
"hpg5lfg/1j/973e4t/hqc/mioxx4h9tgcc9gqw0j8z2fj2covf6nsplrwggyjsg4swmh0glzy2jji4n2gspvb2vlki7zmu81046hvgt4fstlk6fldv0p1w3nf7o6.css",
|
10
|
+
"k6hly56/mh/ri2pa1/04/0afdks3r6k1mbf64xzuwh5efkuxurro63rbckjssmz9mdratf6ayfduqpb0r9qxx2mgnrs0thi0ohh4qtfylfd6cd506zawwic0u3ec0iluu4myn.map",
|
11
|
+
"k6hly56/mh/ri2pa1/04/5k8mnvwxe7hmvp1n932o4mn2b25gqrxfrbe4jfjbig6kzhphnsfkrtqruypfzl93u0ohlv9yyxcoxn6jg6iv5ml8e27jdqjiikyq3.js"
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
FILENAMES.each do |filename|
|
15
|
+
first, *, last = filename.split("/")
|
16
|
+
last = File.extname(last)
|
17
|
+
|
18
|
+
define_method :"test_issue_62_path_#{first}_#{last}" do
|
19
|
+
file_map = {filename => "Test content for #{File.basename(filename)}"}
|
20
|
+
files = roundtrip_tar_string(file_map)
|
21
|
+
|
22
|
+
assert_tar_structure_preserved file_map, files
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_issue_62_full_regression
|
27
|
+
file_map = FILENAMES.each_with_object({}) { |name, map|
|
28
|
+
map[name] = "Test content for #{File.basename(name)}"
|
29
|
+
}
|
30
|
+
files = roundtrip_tar_string(file_map)
|
31
|
+
|
32
|
+
assert_tar_structure_preserved file_map, files
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_issue_62_mixed_filename_lengths_no_regression
|
36
|
+
file_map = MIXED_FILENAME_SCENARIOS.dup
|
37
|
+
FILENAMES.each { file_map[_1] = "content for problematic filename #{_1}" }
|
38
|
+
|
39
|
+
files = roundtrip_tar_string(file_map)
|
40
|
+
|
41
|
+
assert_tar_structure_preserved file_map, files
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_issue_62_very_long_filenames_no_regression
|
45
|
+
file_map = VERY_LONG_FILENAME_SCENARIOS.dup
|
46
|
+
files = roundtrip_tar_string(file_map)
|
47
|
+
|
48
|
+
assert_tar_structure_preserved file_map, files
|
49
|
+
end
|
50
|
+
end
|
data/test/test_minitar.rb
CHANGED
@@ -1,60 +1,189 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "minitar"
|
4
3
|
require "minitest_helper"
|
5
|
-
require "zlib"
|
6
4
|
|
7
5
|
class TestMinitar < Minitest::Test
|
8
|
-
|
6
|
+
SCENARIO = {
|
7
|
+
"path" => nil,
|
8
|
+
"test" => "test",
|
9
|
+
"extra/test" => "extra/test",
|
10
|
+
"empty2004" => {mtime: TIME_2004, mode: 0o755, data: ""},
|
11
|
+
"notime" => {mtime: nil, data: "notime"}
|
12
|
+
}
|
9
13
|
|
10
|
-
def
|
11
|
-
|
12
|
-
["path", nil],
|
13
|
-
["test", "test"],
|
14
|
-
["extra/test", "extra/test"],
|
15
|
-
[{name: "empty2004", mtime: FILE_2004, mode: 0o755}, ""]
|
16
|
-
]
|
14
|
+
def test_minitar_open_r
|
15
|
+
count = 0
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
open_fixture("test_minitar") do |fixture|
|
18
|
+
Minitar.open(fixture, "r") do |stream|
|
19
|
+
stream.each do |entry|
|
20
|
+
assert_kind_of Minitar::Reader::EntryStream, entry
|
21
|
+
|
22
|
+
assert SCENARIO.has_key?(entry.name), "#{entry.name} not expected"
|
23
|
+
|
24
|
+
expected = SCENARIO[entry.name]
|
25
|
+
|
26
|
+
case expected
|
27
|
+
when nil
|
28
|
+
assert_equal 0, entry.size
|
29
|
+
assert_modes_equal 0o755, entry.mode, entry.name
|
30
|
+
assert entry.directory?, "#{entry.name} should be a directory"
|
31
|
+
when String
|
32
|
+
assert_equal expected.length, entry.size, entry.name
|
33
|
+
assert_modes_equal 0o644, entry.mode, entry.name
|
34
|
+
assert entry.file?, "#{entry.name} should be a file"
|
35
|
+
|
36
|
+
if entry.size.zero?
|
37
|
+
assert_nil entry.read
|
38
|
+
else
|
39
|
+
assert_equal expected, entry.read
|
40
|
+
end
|
41
|
+
when Hash
|
42
|
+
if expected[:data].nil?
|
43
|
+
assert_equal 0, entry.size
|
44
|
+
assert_modes_equal (expected[:mode] || 0o755), entry.mode, entry.name
|
45
|
+
assert entry.directory?, "#{entry.name} should be a directory"
|
46
|
+
else
|
47
|
+
assert_equal expected[:data].length, entry.size
|
48
|
+
assert_modes_equal (expected[:mode] || 0o644), entry.mode, entry.name
|
49
|
+
assert entry.file?, "#{entry.name} should be a file"
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal expected[:mtime], entry.mtime if expected[:mtime]
|
53
|
+
|
54
|
+
if entry.size.zero?
|
55
|
+
assert_nil entry.read
|
56
|
+
else
|
57
|
+
assert_equal expected[:data], entry.read
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
count += 1
|
62
|
+
end
|
22
63
|
end
|
23
64
|
end
|
24
65
|
|
25
|
-
|
26
|
-
|
27
|
-
{name: "test", size: 4, mode: 0o644, data: "test"},
|
28
|
-
{name: "extra/test", size: 10, mode: 0o0644, data: "extra/test"},
|
29
|
-
{name: "empty2004", size: 0, mode: 0o755, mtime: FILE_2004, nil: true}
|
30
|
-
]
|
66
|
+
assert_equal SCENARIO.size, count
|
67
|
+
end
|
31
68
|
|
32
|
-
|
33
|
-
|
34
|
-
Minitar.open(reader) do |stream|
|
35
|
-
stream.each.with_index do |entry, i|
|
36
|
-
assert_kind_of Minitar::Reader::EntryStream, entry
|
69
|
+
def test_minitar_open_w
|
70
|
+
events = []
|
37
71
|
|
38
|
-
|
39
|
-
|
40
|
-
|
72
|
+
writer = StringIO.new
|
73
|
+
Minitar.open(writer, "w") do |stream|
|
74
|
+
SCENARIO.each_pair do |name, data|
|
75
|
+
name, data =
|
76
|
+
if data.is_a?(Hash)
|
77
|
+
name = data.merge(name: name)
|
78
|
+
[name, name.delete(:data)]
|
79
|
+
else
|
80
|
+
[name, data]
|
81
|
+
end
|
41
82
|
|
42
|
-
|
43
|
-
|
83
|
+
Minitar.pack_as_file(name, data, stream) do |op, entry_name, stats|
|
84
|
+
events << {
|
85
|
+
name: name,
|
86
|
+
data: data,
|
87
|
+
op: op,
|
88
|
+
entry_name: entry_name,
|
89
|
+
stats: stats
|
90
|
+
}
|
44
91
|
end
|
92
|
+
end
|
93
|
+
end
|
45
94
|
|
46
|
-
|
47
|
-
assert_equal expected[i][:data], entry.read
|
48
|
-
end
|
95
|
+
assert_equal 5120, writer.string.length
|
49
96
|
|
50
|
-
|
51
|
-
|
52
|
-
|
97
|
+
events.each do |event|
|
98
|
+
if event[:name].is_a?(Hash)
|
99
|
+
assert_equal event[:name][:name], event[:entry_name]
|
100
|
+
else
|
101
|
+
assert_equal event[:name], event[:entry_name]
|
102
|
+
end
|
53
103
|
|
54
|
-
|
104
|
+
case [event[:op], event[:entry_name]]
|
105
|
+
in [:dir, "path"]
|
106
|
+
assert_equal 0, event[:stats][:size]
|
107
|
+
assert_equal 493, event[:stats][:mode]
|
108
|
+
|
109
|
+
in [:file_start, "test"]
|
110
|
+
assert_equal 4, event[:stats][:size]
|
111
|
+
assert_equal 420, event[:stats][:mode]
|
112
|
+
assert_equal 4, event[:stats][:current]
|
113
|
+
assert_equal 4, event[:stats][:currinc]
|
114
|
+
assert_equal "test", event[:data]
|
115
|
+
in [:file_progress, "test"]
|
116
|
+
assert_equal 4, event[:stats][:size]
|
117
|
+
assert_equal 420, event[:stats][:mode]
|
118
|
+
assert_equal 4, event[:stats][:current]
|
119
|
+
assert_equal 4, event[:stats][:currinc]
|
120
|
+
assert_equal "test", event[:data]
|
121
|
+
in [:file_done, "test"]
|
122
|
+
assert_equal 4, event[:stats][:size]
|
123
|
+
assert_equal 420, event[:stats][:mode]
|
124
|
+
assert_equal 4, event[:stats][:current]
|
125
|
+
assert_equal 4, event[:stats][:currinc]
|
126
|
+
assert_equal "test", event[:data]
|
127
|
+
|
128
|
+
in [:file_start, "extra/test"]
|
129
|
+
assert_equal 10, event[:stats][:size]
|
130
|
+
assert_equal 420, event[:stats][:mode]
|
131
|
+
assert_equal 10, event[:stats][:current]
|
132
|
+
assert_equal 10, event[:stats][:currinc]
|
133
|
+
assert_equal "extra/test", event[:data]
|
134
|
+
in [:file_progress, "extra/test"]
|
135
|
+
assert_equal 10, event[:stats][:size]
|
136
|
+
assert_equal 420, event[:stats][:mode]
|
137
|
+
assert_equal 10, event[:stats][:current]
|
138
|
+
assert_equal 10, event[:stats][:currinc]
|
139
|
+
assert_equal "extra/test", event[:data]
|
140
|
+
in [:file_done, "extra/test"]
|
141
|
+
assert_equal 10, event[:stats][:size]
|
142
|
+
assert_equal 420, event[:stats][:mode]
|
143
|
+
assert_equal 10, event[:stats][:current]
|
144
|
+
assert_equal 10, event[:stats][:currinc]
|
145
|
+
assert_equal "extra/test", event[:data]
|
146
|
+
|
147
|
+
in [:file_start, "empty2004"]
|
148
|
+
assert_equal 0, event[:stats][:size]
|
149
|
+
assert_equal 493, event[:stats][:mode]
|
150
|
+
assert_equal 0, event[:stats][:current]
|
151
|
+
assert_equal 1072915200, event[:stats][:mtime]
|
152
|
+
assert_equal "", event[:data]
|
153
|
+
in [:file_done, "empty2004"]
|
154
|
+
assert_equal 0, event[:stats][:size]
|
155
|
+
assert_equal 493, event[:stats][:mode]
|
156
|
+
assert_equal 0, event[:stats][:current]
|
157
|
+
assert_equal 1072915200, event[:stats][:mtime]
|
158
|
+
assert_equal "", event[:data]
|
159
|
+
|
160
|
+
in [:file_start, "notime"]
|
161
|
+
assert_equal 6, event[:stats][:size]
|
162
|
+
assert_equal 420, event[:stats][:mode]
|
163
|
+
assert_equal 6, event[:stats][:current]
|
164
|
+
assert_equal 6, event[:stats][:currinc]
|
165
|
+
assert_equal "notime", event[:data]
|
166
|
+
in [:file_progress, "notime"]
|
167
|
+
assert_equal 6, event[:stats][:size]
|
168
|
+
assert_equal 420, event[:stats][:mode]
|
169
|
+
assert_equal 6, event[:stats][:current]
|
170
|
+
assert_equal 6, event[:stats][:currinc]
|
171
|
+
assert_equal "notime", event[:data]
|
172
|
+
in [:file_done, "notime"]
|
173
|
+
assert_equal 6, event[:stats][:size]
|
174
|
+
assert_equal 420, event[:stats][:mode]
|
175
|
+
assert_equal 6, event[:stats][:current]
|
176
|
+
assert_equal 6, event[:stats][:currinc]
|
177
|
+
assert_equal "notime", event[:data]
|
178
|
+
else
|
179
|
+
raise "Unknown operation #{event[:op].inspect} for #{event[:entry_name].inspect}"
|
55
180
|
end
|
56
181
|
end
|
182
|
+
end
|
57
183
|
|
58
|
-
|
184
|
+
def test_minitar_x
|
185
|
+
assert_raises(ArgumentError) do
|
186
|
+
Minitar.open("foo", "x")
|
187
|
+
end
|
59
188
|
end
|
60
189
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "minitest_helper"
|
2
|
+
|
3
|
+
class TestPaxHeader < Minitest::Test
|
4
|
+
def test_from_stream_with_size_attribute
|
5
|
+
pax_content = "19 size=8614356715\n28 mtime=1749098832.3200000\n"
|
6
|
+
pax_header = create_pax_header_from_stream(pax_content)
|
7
|
+
|
8
|
+
assert_equal 8614356715, pax_header.size
|
9
|
+
assert_equal "1749098832.3200000", pax_header.attributes["mtime"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_from_stream_without_size_attribute
|
13
|
+
pax_content = "28 mtime=1749098832.3200000\n27 path=some/long/path.txt\n"
|
14
|
+
pax_header = create_pax_header_from_stream(pax_content)
|
15
|
+
|
16
|
+
assert_nil pax_header.size
|
17
|
+
assert_equal "some/long/path.txt", pax_header.path
|
18
|
+
assert_equal 1749098832.32, pax_header.mtime
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_parse_multiline_values
|
22
|
+
pax_content = "22 foo=one\ntwo\nthree\n\n12 bar=four\n"
|
23
|
+
pax_header = Minitar::PaxHeader.from_data(pax_content)
|
24
|
+
assert_equal "one\ntwo\nthree\n", pax_header.attributes["foo"]
|
25
|
+
assert_equal "four", pax_header.attributes["bar"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_from_stream_with_invalid_header
|
29
|
+
header_data = build_tar_file_header("regular_file.txt", "", 0o644, 100)
|
30
|
+
io = StringIO.new(header_data)
|
31
|
+
|
32
|
+
posix_header = Minitar::PosixHeader.from_stream(io)
|
33
|
+
refute posix_header.pax_header?
|
34
|
+
|
35
|
+
assert_raises(ArgumentError, "Header must be a PAX header") do
|
36
|
+
Minitar::PaxHeader.from_stream(io, posix_header)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_parse_content_with_multiple_attributes
|
41
|
+
pax_content = "19 size=8614356715\n28 mtime=1749098832.3200000\n27 path=some/long/path.txt\n"
|
42
|
+
|
43
|
+
pax_header = Minitar::PaxHeader.from_data(pax_content)
|
44
|
+
|
45
|
+
assert_equal 8614356715, pax_header.size
|
46
|
+
assert_equal "some/long/path.txt", pax_header.path
|
47
|
+
assert_equal 1749098832.32, pax_header.mtime
|
48
|
+
|
49
|
+
# Check raw attributes
|
50
|
+
assert_equal "8614356715", pax_header.attributes["size"]
|
51
|
+
assert_equal "1749098832.3200000", pax_header.attributes["mtime"]
|
52
|
+
assert_equal "some/long/path.txt", pax_header.attributes["path"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_parse_content_with_invalid_length_format
|
56
|
+
assert_raises(ArgumentError) do
|
57
|
+
Minitar::PaxHeader.from_data("19 size=8614356715\ninvalid line\n23 path=valid/path.txt\n")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_parse_content_with_oversized_record
|
62
|
+
assert_raises(ArgumentError) do
|
63
|
+
Minitar::PaxHeader.from_data("19 size=8614356715\n999 toolong=value\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_from_stream_strips_padding
|
68
|
+
pax_content = "19 size=8614356715\n"
|
69
|
+
pax_header = create_pax_header_from_stream(pax_content)
|
70
|
+
|
71
|
+
# Should parse only the actual content, ignoring padding
|
72
|
+
assert_equal 8614356715, pax_header.size
|
73
|
+
assert_equal 1, pax_header.attributes.size # Only one attribute parsed
|
74
|
+
|
75
|
+
# Should have parsed content correctly
|
76
|
+
assert_equal 1, pax_header.attributes.size
|
77
|
+
assert_equal "8614356715", pax_header.attributes["size"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_attributes_accessor
|
81
|
+
pax_content = "19 size=8614356715\n23 custom=custom_value\n"
|
82
|
+
pax_header = Minitar::PaxHeader.from_data(pax_content)
|
83
|
+
|
84
|
+
assert_equal "8614356715", pax_header.attributes["size"]
|
85
|
+
assert_equal "custom_value", pax_header.attributes["custom"]
|
86
|
+
assert_nil pax_header.attributes["nonexistent"]
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_pax_header_to_s
|
90
|
+
pax_header = Minitar::PaxHeader.new(size: "8614356715", mtime: "1749098832.3200000")
|
91
|
+
assert_equal "19 size=8614356715\n28 mtime=1749098832.3200000\n", pax_header.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def create_pax_header_from_stream(pax_content, name = "./PaxHeaders.X/test_file")
|
97
|
+
pax_header_data = build_tar_pax_header(name, "", pax_content.bytesize)
|
98
|
+
padded_content = pax_content.ljust((pax_content.bytesize / 512.0).ceil * 512, "\0")
|
99
|
+
io = StringIO.new(pax_header_data + padded_content)
|
100
|
+
|
101
|
+
posix_header = Minitar::PosixHeader.from_stream(io)
|
102
|
+
Minitar::PaxHeader.from_stream(io, posix_header)
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "minitest_helper"
|
2
|
+
|
3
|
+
class TestPaxSupport < Minitest::Test
|
4
|
+
def test_pax_header_size_extraction_in_reader
|
5
|
+
pax_content = "16 size=1048576\n28 mtime=1749098832.3200000\n"
|
6
|
+
tar_data = create_pax_with_file_headers(pax_content, "./PaxHeaders.X/large_file.mov", "large_file.mov", 1048576, 0)
|
7
|
+
|
8
|
+
entries = read_tar_entries(tar_data)
|
9
|
+
assert_equal 1, entries.size
|
10
|
+
|
11
|
+
entry = entries.first
|
12
|
+
assert_equal "large_file.mov", entry.name
|
13
|
+
assert_equal 1048576, entry.size # Size from PAX header
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_pax_header_without_size_uses_header_size
|
17
|
+
pax_content = "28 mtime=1749098832.3200000\n"
|
18
|
+
tar_data = create_pax_with_file_headers(pax_content, "./PaxHeaders.X/normal_file.txt", "normal_file.txt", 12345, 12345)
|
19
|
+
|
20
|
+
entries = read_tar_entries(tar_data)
|
21
|
+
assert_equal 1, entries.size
|
22
|
+
|
23
|
+
entry = entries.first
|
24
|
+
assert_equal "normal_file.txt", entry.name
|
25
|
+
assert_equal 12345, entry.size # Original header size preserved
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_pax_header_takes_precedence_over_posix_header_size
|
29
|
+
pax_content = "16 size=1048576\n28 mtime=1749098832.3200000\n"
|
30
|
+
tar_data = create_pax_with_file_headers(pax_content, "./PaxHeaders.X/precedence_file.txt", "precedence_file.txt", 12345, 12345)
|
31
|
+
|
32
|
+
entries = read_tar_entries(tar_data)
|
33
|
+
assert_equal 1, entries.size
|
34
|
+
|
35
|
+
entry = entries.first
|
36
|
+
assert_equal "precedence_file.txt", entry.name
|
37
|
+
assert_equal 1048576, entry.size # PAX size takes precedence over POSIX size (12345)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_pax_size_extraction_logic
|
41
|
+
pax_header_with_size = Minitar::PaxHeader.new(size: "1048576", mtime: "1749098832.3200000")
|
42
|
+
assert_equal 1048576, pax_header_with_size.size
|
43
|
+
|
44
|
+
pax_header_without_size = Minitar::PaxHeader.new(mtime: "1749098832.3200000")
|
45
|
+
assert_nil pax_header_without_size.size
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def read_tar_entries(tar_data)
|
51
|
+
io = StringIO.new(tar_data)
|
52
|
+
Minitar::Reader.open(io, &:to_a)
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_pax_with_file_headers(pax_content, pax_name, file_name, file_size, posix_header_file_size)
|
56
|
+
file_content = "x" * file_size
|
57
|
+
padded_file_content = file_content.ljust((file_size / 512.0).ceil * 512, "\0")
|
58
|
+
|
59
|
+
[
|
60
|
+
build_tar_pax_header(pax_name, "", pax_content.bytesize),
|
61
|
+
pax_content.ljust((pax_content.bytesize / 512.0).ceil * 512, "\0"),
|
62
|
+
build_tar_file_header(file_name, "", 0o644, file_size),
|
63
|
+
padded_file_content
|
64
|
+
].join
|
65
|
+
end
|
66
|
+
end
|