ruby_apk 0.4.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.
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +38 -0
- data/LICENSE.txt +22 -0
- data/README.md +106 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/android/apk.rb +155 -0
- data/lib/android/axml_parser.rb +178 -0
- data/lib/android/dex/access_flag.rb +74 -0
- data/lib/android/dex/dex_object.rb +435 -0
- data/lib/android/dex/info.rb +151 -0
- data/lib/android/dex/utils.rb +45 -0
- data/lib/android/dex.rb +92 -0
- data/lib/android/manifest.rb +209 -0
- data/lib/android/resource.rb +135 -0
- data/lib/android/utils.rb +55 -0
- data/lib/ruby_apk.rb +6 -0
- data/ruby_apk.gemspec +93 -0
- data/spec/apk_spec.rb +268 -0
- data/spec/axml_parser_spec.rb +46 -0
- data/spec/data/sample.apk +0 -0
- data/spec/data/sample_AndroidManifest.xml +0 -0
- data/spec/data/sample_classes.dex +0 -0
- data/spec/data/sample_resources.arsc +0 -0
- data/spec/data/sample_resources_utf16.arsc +0 -0
- data/spec/dex/access_flag_spec.rb +42 -0
- data/spec/dex/dex_object_spec.rb +103 -0
- data/spec/dex/info_spec.rb +121 -0
- data/spec/dex/utils_spec.rb +56 -0
- data/spec/dex_spec.rb +59 -0
- data/spec/manifest_spec.rb +179 -0
- data/spec/resource_spec.rb +87 -0
- data/spec/ruby_apk_spec.rb +4 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/utils_spec.rb +90 -0
- metadata +198 -0
data/spec/apk_spec.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'tempfile'
|
3
|
+
require 'zip/zip'
|
4
|
+
require 'digest/sha1'
|
5
|
+
require 'digest/sha2'
|
6
|
+
require 'digest/md5'
|
7
|
+
|
8
|
+
class TempApk
|
9
|
+
attr_reader :path
|
10
|
+
def initialize
|
11
|
+
@tmp = Tempfile.open('apk_spec')
|
12
|
+
@path = @tmp.path
|
13
|
+
@tmp.close! # delete file
|
14
|
+
append("AndroidManifest.xml", "hogehoge")
|
15
|
+
append("resources.arsc", "hogehoge")
|
16
|
+
end
|
17
|
+
def destroy!
|
18
|
+
File.unlink(@path) if File.exist? @path
|
19
|
+
end
|
20
|
+
def append(entry_name, data)
|
21
|
+
Zip::ZipFile.open(@path, Zip::ZipFile::CREATE) { |zip|
|
22
|
+
zip.get_output_stream(entry_name) {|f| f.write data }
|
23
|
+
}
|
24
|
+
end
|
25
|
+
def remove(entry_name)
|
26
|
+
Zip::ZipFile.open(@path, Zip::ZipFile::CREATE) { |zip|
|
27
|
+
zip.remove(entry_name)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe Android::Apk do
|
33
|
+
before do
|
34
|
+
$stderr.reopen('/dev/null','w')
|
35
|
+
end
|
36
|
+
let(:tmp_apk) { TempApk.new }
|
37
|
+
let(:tmp_path) { tmp_apk.path }
|
38
|
+
let(:apk) { Android::Apk.new(tmp_path) }
|
39
|
+
subject { apk }
|
40
|
+
|
41
|
+
after do
|
42
|
+
tmp_apk.destroy!
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#initialize" do
|
46
|
+
let(:path) { tmp_path }
|
47
|
+
subject { Android::Apk.new(path) }
|
48
|
+
context "with not exist path" do
|
49
|
+
let(:path) { "not exist path" }
|
50
|
+
it { expect{ subject }.to raise_error Android::NotFoundError }
|
51
|
+
end
|
52
|
+
context "with not zip file path" do
|
53
|
+
let(:path) { __FILE__ } # not zip file
|
54
|
+
it { expect{ subject }.to raise_error Android::NotApkFileError }
|
55
|
+
end
|
56
|
+
context "with zip(and non apk) file" do
|
57
|
+
before do
|
58
|
+
tmp_apk.append('hoge.txt', 'hogehoge')
|
59
|
+
tmp_apk.remove('AndroidManifest.xml')
|
60
|
+
end
|
61
|
+
it { expect{ subject }.to raise_error Android::NotApkFileError }
|
62
|
+
end
|
63
|
+
context "with zip includes AndroidManifest.xml" do
|
64
|
+
it { should be_a_instance_of Android::Apk }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#path" do
|
69
|
+
subject { apk.path }
|
70
|
+
it "should equals initialized path" do
|
71
|
+
should == tmp_path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#manifest" do
|
76
|
+
subject { apk.manifest }
|
77
|
+
|
78
|
+
context "when Manifest parse is succeeded." do
|
79
|
+
let(:mock_mani) { mock(Android::Manifest) }
|
80
|
+
|
81
|
+
before do
|
82
|
+
end
|
83
|
+
it "should return manifest object" do
|
84
|
+
Android::Manifest.should_receive(:new).and_return(mock_mani)
|
85
|
+
subject.should == mock_mani
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when Manifest parse is failed" do
|
90
|
+
it 'should return nil' do
|
91
|
+
Android::Manifest.should_receive(:new).and_raise(Android::AXMLParser::ReadError)
|
92
|
+
subject.should be_nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#dex" do
|
98
|
+
let(:mock_dex) { mock(Android::Dex) }
|
99
|
+
subject { apk.dex }
|
100
|
+
context "when there is no dex file" do
|
101
|
+
it { should be_nil }
|
102
|
+
end
|
103
|
+
context "when invalid dex file" do
|
104
|
+
before do
|
105
|
+
tmp_apk.append("classes.dex", "invalid dex")
|
106
|
+
end
|
107
|
+
it { should be_nil }
|
108
|
+
end
|
109
|
+
context "with mock classes.dex file" do
|
110
|
+
before do
|
111
|
+
tmp_apk.append("classes.dex", "mock data")
|
112
|
+
end
|
113
|
+
it "should return mock value" do
|
114
|
+
Android::Dex.should_receive(:new).with("mock data").and_return(mock_dex)
|
115
|
+
subject.should == mock_dex
|
116
|
+
end
|
117
|
+
end
|
118
|
+
context "with real classes.dex file" do
|
119
|
+
before do
|
120
|
+
dex_path = File.expand_path(File.dirname(__FILE__) + '/data/sample_classes.dex')
|
121
|
+
tmp_apk.append("classes.dex", File.open(dex_path, "rb") {|f| f.read })
|
122
|
+
end
|
123
|
+
it { should be_instance_of Android::Dex }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
its(:bindata) { should be_instance_of String }
|
128
|
+
describe '#bindata' do
|
129
|
+
specify 'encoding should be ASCII-8BIT' do
|
130
|
+
subject.bindata.encoding.should eq Encoding::ASCII_8BIT
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#resource' do
|
135
|
+
let(:mock_rsc) { mock(Android::Resource) }
|
136
|
+
subject { apk.resource }
|
137
|
+
it "should return manifest object" do
|
138
|
+
Android::Resource.should_receive(:new).and_return(mock_rsc)
|
139
|
+
subject.should == mock_rsc
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#size" do
|
144
|
+
subject { apk.size }
|
145
|
+
it "should return apk file size" do
|
146
|
+
should == File.size(tmp_path)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "#digest" do
|
151
|
+
let(:data) { File.open(tmp_apk.path, 'rb'){|f| f.read } }
|
152
|
+
subject { apk.digest(type) }
|
153
|
+
context "when type is sha1" do
|
154
|
+
let(:type) { :sha1 }
|
155
|
+
it "should return sha1 digest" do
|
156
|
+
should eq Digest::SHA1.hexdigest(data)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
context "when type is sha256" do
|
160
|
+
let(:type) { :sha256 }
|
161
|
+
it "should return sha256 digest" do
|
162
|
+
should == Digest::SHA256.hexdigest(data)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
context "when type is md5" do
|
166
|
+
let(:type) { :md5 }
|
167
|
+
it "should return md5 digest" do
|
168
|
+
should == Digest::MD5.hexdigest(data)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
context "when type is unkown symbol" do
|
172
|
+
let(:type) { :unknown }
|
173
|
+
it do
|
174
|
+
expect { subject }.to raise_error(ArgumentError)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
context "when type is not symbol(String: 'sha1')" do
|
178
|
+
let(:type) { 'sha1' }
|
179
|
+
it { expect { subject }.to raise_error(ArgumentError) }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#time' do
|
184
|
+
subject { apk.time }
|
185
|
+
it { should be_kind_of Time }
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "#each_file" do
|
189
|
+
before do
|
190
|
+
tmp_apk.append("hoge.txt", "aaaaaaa")
|
191
|
+
end
|
192
|
+
it { expect { |b| apk.each_file(&b) }.to yield_successive_args(Array, Array, Array) }
|
193
|
+
let(:each_file_result ) {
|
194
|
+
result = []
|
195
|
+
apk.each_file do |name, data|
|
196
|
+
result << [name, data]
|
197
|
+
end
|
198
|
+
result
|
199
|
+
}
|
200
|
+
|
201
|
+
it "should invoke block with all file" do
|
202
|
+
each_file_result.should have(3).items
|
203
|
+
each_file_result.should include(['AndroidManifest.xml', 'hogehoge'])
|
204
|
+
each_file_result.should include(['hoge.txt','aaaaaaa'])
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe '#file' do
|
209
|
+
let(:data) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaa' }
|
210
|
+
let(:path) { 'hoge.txt' }
|
211
|
+
subject { apk.file(path) }
|
212
|
+
|
213
|
+
before do
|
214
|
+
tmp_apk.append('hoge.txt', data)
|
215
|
+
end
|
216
|
+
context 'assigns exist path' do
|
217
|
+
it 'should equal file data' do
|
218
|
+
should eq data
|
219
|
+
end
|
220
|
+
end
|
221
|
+
context 'assigns not exist path' do
|
222
|
+
let(:path) { 'not_exist_path.txt' }
|
223
|
+
it { expect { subject }.to raise_error(Android::NotFoundError) }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe '#each_entry' do
|
228
|
+
before do
|
229
|
+
tmp_apk.append("hoge.txt", "aaaaaaa")
|
230
|
+
end
|
231
|
+
it { expect { |b| apk.each_entry(&b) }.to yield_successive_args(Zip::ZipEntry, Zip::ZipEntry, Zip::ZipEntry) }
|
232
|
+
end
|
233
|
+
|
234
|
+
describe '#entry' do
|
235
|
+
subject { apk.entry(entry_name) }
|
236
|
+
context 'assigns exist entry' do
|
237
|
+
let(:entry_name) { 'AndroidManifest.xml' }
|
238
|
+
it { should be_instance_of Zip::ZipEntry }
|
239
|
+
end
|
240
|
+
context 'assigns not exist entry name' do
|
241
|
+
let(:entry_name) { 'not_exist_path' }
|
242
|
+
it { expect{ subject }.to raise_error(Android::NotFoundError) }
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe "#find" do
|
247
|
+
before do
|
248
|
+
tmp_apk.append("hoge.txt", "aaaaaaa")
|
249
|
+
end
|
250
|
+
it "should return matched array" do
|
251
|
+
array = apk.find do |name, data|
|
252
|
+
name == "hoge.txt"
|
253
|
+
end
|
254
|
+
array.should be_instance_of Array
|
255
|
+
array.should have(1).item
|
256
|
+
array[0] == "hoge.txt" # returns filename
|
257
|
+
end
|
258
|
+
context "when no entry is matched" do
|
259
|
+
it "should return emtpy array" do
|
260
|
+
array = apk.find do |name, dota|
|
261
|
+
false # nothing matched
|
262
|
+
end
|
263
|
+
array.should be_instance_of Array
|
264
|
+
array.should be_empty
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
3
|
+
|
4
|
+
describe Android::AXMLParser do
|
5
|
+
let(:bin_xml_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_AndroidManifest.xml') }
|
6
|
+
let(:bin_xml){ File.open(bin_xml_path, 'rb') {|f| f.read } }
|
7
|
+
let(:axmlparser){ Android::AXMLParser.new(bin_xml) }
|
8
|
+
|
9
|
+
describe "#parse" do
|
10
|
+
|
11
|
+
subject { axmlparser.parse }
|
12
|
+
context 'with sample_AndroidManifest.xml' do
|
13
|
+
it { should be_instance_of(REXML::Document) }
|
14
|
+
specify 'root element should be <manifest> element' do
|
15
|
+
subject.root.name.should eq 'manifest'
|
16
|
+
end
|
17
|
+
specify 'should have 2 <uses-permission> elements' do
|
18
|
+
subject.get_elements('/manifest/uses-permission').should have(2).items
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with nil data as binary xml' do
|
23
|
+
let(:bin_xml) { nil }
|
24
|
+
specify { expect{ subject }.to raise_error }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#strings" do
|
30
|
+
context 'with sample_AndroidManifest.xml' do
|
31
|
+
subject { axmlparser.strings }
|
32
|
+
before do
|
33
|
+
axmlparser.parse
|
34
|
+
end
|
35
|
+
it { should be_instance_of(Array) }
|
36
|
+
|
37
|
+
# ugh!! the below test cases depend on sample_AndroidManifest.xml
|
38
|
+
it { should have(26).items} # in sample manifest.
|
39
|
+
it { should include("versionCode") }
|
40
|
+
it { should include("versionName") }
|
41
|
+
it { should include("minSdkVersion") }
|
42
|
+
it { should include("package") }
|
43
|
+
it { should include("manifest") }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Android::Dex::AccessFlag do
|
4
|
+
let(:flag) { 0x01 }
|
5
|
+
subject { Android::Dex::AccessFlag.new(flag) }
|
6
|
+
its(:flag) { should eq flag }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Android::Dex::ClassAccessFlag do
|
10
|
+
let(:accessor) { Android::Dex::ClassAccessFlag.new(flags) }
|
11
|
+
|
12
|
+
subject { accessor.to_s }
|
13
|
+
context 'flags are 0x19(public static final)' do
|
14
|
+
let(:flags) { 0x19 }
|
15
|
+
it { should eq 'public static final' }
|
16
|
+
end
|
17
|
+
context 'flags are 0xc0(volatile transient)' do
|
18
|
+
let(:flags) { 0xc0 }
|
19
|
+
it { should eq 'volatile transient' }
|
20
|
+
end
|
21
|
+
context 'flags are 0x22(private synchronized)' do
|
22
|
+
let(:flags) { 0x22 }
|
23
|
+
it { should eq 'private synchronized' }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Android::Dex::MethodAccessFlag do
|
28
|
+
let(:accessor) { Android::Dex::MethodAccessFlag.new(flags) }
|
29
|
+
subject { accessor.to_s }
|
30
|
+
context 'flags are 0x19(public static final)' do
|
31
|
+
let(:flags) { 0x19 }
|
32
|
+
it { should eq 'public static final' }
|
33
|
+
end
|
34
|
+
context 'flags are 0xc0(bridge varargs)' do
|
35
|
+
let(:flags) { 0xc0 }
|
36
|
+
it { should eq 'bridge varargs' }
|
37
|
+
end
|
38
|
+
context 'flags are 0x22(private synchronized)' do
|
39
|
+
let(:flags) { 0x22 }
|
40
|
+
it { should eq 'private synchronized' }
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Android::Dex do
|
4
|
+
describe Android::Dex::DexObject::Header do
|
5
|
+
let(:header_sample) {
|
6
|
+
"\x64\x65\x78\x0A\x30\x33\x35\x00\x3F\x14\x98\x2C\x25\x77\x9B\x8D" +
|
7
|
+
"\x7C\xF0\x0B\xFA\x4D\x7B\x03\xAD\x4C\x15\xBC\x31\x4F\xD3\x4B\x71" +
|
8
|
+
"\x58\x18\x00\x00\x70\x00\x00\x00\x78\x56\x34\x12\x00\x00\x00\x00" +
|
9
|
+
"\x00\x00\x00\x00\x88\x17\x00\x00\x7A\x00\x00\x00\x70\x00\x00\x00" +
|
10
|
+
"\x23\x00\x00\x00\x58\x02\x00\x00\x0E\x00\x00\x00\xE4\x02\x00\x00" +
|
11
|
+
"\x10\x00\x00\x00\x8C\x03\x00\x00\x2C\x00\x00\x00\x0C\x04\x00\x00" +
|
12
|
+
"\x0A\x00\x00\x00\x6C\x05\x00\x00\xAC\x11\x00\x00\xAC\x06\x00\x00"
|
13
|
+
}
|
14
|
+
let(:header) { Android::Dex::DexObject::Header.new(header_sample) }
|
15
|
+
describe "#symbols" do
|
16
|
+
subject { header.symbols }
|
17
|
+
it { should be_kind_of(Array) }
|
18
|
+
it { should have(23).items }
|
19
|
+
it { should include(:magic) }
|
20
|
+
it { should include(:checksum) }
|
21
|
+
it { should include(:signature) }
|
22
|
+
it { should include(:file_size) }
|
23
|
+
it { should include(:header_size) }
|
24
|
+
it { should include(:endian_tag) }
|
25
|
+
it { should include(:link_size) }
|
26
|
+
it { should include(:link_off) }
|
27
|
+
it { should include(:map_off) }
|
28
|
+
it { should include(:string_ids_size) }
|
29
|
+
it { should include(:string_ids_off) }
|
30
|
+
it { should include(:type_ids_size) }
|
31
|
+
it { should include(:type_ids_off) }
|
32
|
+
it { should include(:proto_ids_size) }
|
33
|
+
it { should include(:proto_ids_off) }
|
34
|
+
it { should include(:field_ids_size) }
|
35
|
+
it { should include(:field_ids_off) }
|
36
|
+
it { should include(:method_ids_size) }
|
37
|
+
it { should include(:method_ids_off) }
|
38
|
+
it { should include(:class_defs_size) }
|
39
|
+
it { should include(:class_defs_off) }
|
40
|
+
it { should include(:data_size) }
|
41
|
+
it { should include(:data_off) }
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#[]" do
|
45
|
+
subject { header }
|
46
|
+
it ':magic should be "dex\n035\0"' do
|
47
|
+
subject[:magic].should == "dex\n035\0"
|
48
|
+
end
|
49
|
+
it ":checksum should be 748164159(this value depends on sample_classes.dex)" do
|
50
|
+
subject[:checksum].should == 748164159
|
51
|
+
end
|
52
|
+
it ":signature should be 20byte string" do
|
53
|
+
subject[:signature].should be_kind_of String
|
54
|
+
subject[:signature].length == 20
|
55
|
+
end
|
56
|
+
it ":file_size should be classes.dex file size" do
|
57
|
+
subject[:file_size].should == 6232
|
58
|
+
end
|
59
|
+
it ":header_size should be 0x70" do
|
60
|
+
subject[:header_size].should == 0x70
|
61
|
+
end
|
62
|
+
it "should have integer params" do
|
63
|
+
subject[:header_size].should == 0x70
|
64
|
+
end
|
65
|
+
context "with int symbols" do
|
66
|
+
before do
|
67
|
+
@params = [
|
68
|
+
:link_size,
|
69
|
+
:link_off,
|
70
|
+
:map_off,
|
71
|
+
:string_ids_size,
|
72
|
+
:string_ids_off,
|
73
|
+
:type_ids_size,
|
74
|
+
:type_ids_off,
|
75
|
+
:proto_ids_size,
|
76
|
+
:proto_ids_off,
|
77
|
+
:field_ids_size,
|
78
|
+
:field_ids_off,
|
79
|
+
:method_ids_size,
|
80
|
+
:method_ids_off,
|
81
|
+
:class_defs_size,
|
82
|
+
:class_defs_off,
|
83
|
+
:data_size,
|
84
|
+
:data_off,
|
85
|
+
]
|
86
|
+
end
|
87
|
+
it "should have integer value" do
|
88
|
+
@params.each do |sym|
|
89
|
+
subject[sym].should be_kind_of Integer
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
context "with unkown params" do
|
94
|
+
it { subject[:unkown].should be_nil }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#inspect" do
|
99
|
+
subject { header.inspect }
|
100
|
+
it { should match(/\A<Android::Dex::DexObject::Header.*>\Z/m) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
shared_context 'with sample_classes.dex', with: :sample_dex do
|
4
|
+
let(:dex_path){ File.expand_path(File.dirname(__FILE__) + '/../data/sample_classes.dex') }
|
5
|
+
let(:dex_bin){ File.open(dex_path, 'rb') {|f| f.read } }
|
6
|
+
let(:dex){ Android::Dex.new(dex_bin) }
|
7
|
+
let(:last_class) { dex.classes.last }
|
8
|
+
end
|
9
|
+
describe Android::Dex::ClassInfo do
|
10
|
+
include_context 'with sample_classes.dex'
|
11
|
+
context 'about the last class in Dex#classes with sample_classes.dex' do
|
12
|
+
let(:last_class) { dex.classes.last }
|
13
|
+
its(:name){ should eq 'Lexample/app/sample/SampleCode;' }
|
14
|
+
its(:access_flags){ should be_instance_of Android::Dex::ClassAccessFlag }
|
15
|
+
its(:super_class){ should eq 'Ljava/lang/Object;' }
|
16
|
+
its(:class_data){ should be_instance_of Android::Dex::DexObject::ClassDataItem }
|
17
|
+
its(:class_def){ should be_instance_of Android::Dex::DexObject::ClassDefItem }
|
18
|
+
its(:definition) { should eq 'public class Lexample/app/sample/SampleCode; extends Ljava/lang/Object;' }
|
19
|
+
|
20
|
+
subject { last_class }
|
21
|
+
describe '#static_fields' do
|
22
|
+
subject { last_class.static_fields }
|
23
|
+
it { should have(1).item }
|
24
|
+
specify { subject[0].should be_instance_of Android::Dex::FieldInfo }
|
25
|
+
end
|
26
|
+
describe '#instance_fields' do
|
27
|
+
subject { last_class.instance_fields }
|
28
|
+
it { should have(1).item }
|
29
|
+
specify { subject[0].should be_instance_of Android::Dex::FieldInfo }
|
30
|
+
end
|
31
|
+
describe '#direct_methods' do
|
32
|
+
subject { last_class.direct_methods }
|
33
|
+
it { should have(3).items }
|
34
|
+
specify { subject[0].should be_instance_of Android::Dex::MethodInfo }
|
35
|
+
end
|
36
|
+
describe '#virtual_methods' do
|
37
|
+
subject { last_class.virtual_methods }
|
38
|
+
it { should have(18).items }
|
39
|
+
specify { subject[0].should be_instance_of Android::Dex::MethodInfo }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context 'when class_data_item is nil' do
|
43
|
+
let(:mock_cls_def) {
|
44
|
+
s = double(Android::Dex::DexObject::ClassDefItem)
|
45
|
+
s.stub(:'[]').with(anything()).and_return(0)
|
46
|
+
s.stub(:class_data_item).and_return(nil)
|
47
|
+
s
|
48
|
+
}
|
49
|
+
let(:class_info) { Android::Dex::ClassInfo.new(mock_cls_def, nil) }
|
50
|
+
describe '#static_fields' do
|
51
|
+
subject { class_info.static_fields }
|
52
|
+
it { should be_kind_of Array }
|
53
|
+
it { should be_empty }
|
54
|
+
end
|
55
|
+
describe '#instance_fields' do
|
56
|
+
subject { class_info.instance_fields }
|
57
|
+
it { should be_kind_of Array }
|
58
|
+
it { should be_empty }
|
59
|
+
end
|
60
|
+
describe '#direct_methods' do
|
61
|
+
subject { class_info.direct_methods }
|
62
|
+
it { should be_kind_of Array }
|
63
|
+
it { should be_empty }
|
64
|
+
end
|
65
|
+
describe '#virtual_methods' do
|
66
|
+
subject { class_info.virtual_methods }
|
67
|
+
it { should be_kind_of Array }
|
68
|
+
it { should be_empty }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe Android::Dex::FieldInfo do
|
74
|
+
include_context 'with sample_classes.dex'
|
75
|
+
context 'about the first static field of the last class with sample_classes.dex'do
|
76
|
+
let(:first_static_field) { last_class.static_fields.first }
|
77
|
+
subject { first_static_field }
|
78
|
+
its(:name) { should eq 'TAG' }
|
79
|
+
its(:type) { should eq 'Ljava/lang/String;' }
|
80
|
+
describe '#access_flags' do
|
81
|
+
subject { first_static_field.access_flags }
|
82
|
+
it { should be_instance_of Android::Dex::ClassAccessFlag }
|
83
|
+
specify { subject.to_s.should eq 'private static final' }
|
84
|
+
end
|
85
|
+
describe '#definition' do
|
86
|
+
subject { first_static_field.definition }
|
87
|
+
it { should eq 'private static final Ljava/lang/String; TAG' }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe Android::Dex::MethodInfo do
|
93
|
+
include_context 'with sample_classes.dex'
|
94
|
+
context 'about the first direct method of the last class' do
|
95
|
+
let(:first_direct_method) { last_class.direct_methods.first }
|
96
|
+
subject { first_direct_method }
|
97
|
+
its(:name) { should eq '<init>' }
|
98
|
+
end
|
99
|
+
context 'about the first virtual method of the last class' do
|
100
|
+
let(:first_virtual_method) { last_class.virtual_methods.first }
|
101
|
+
subject { first_virtual_method }
|
102
|
+
its(:name) { should eq 'processDoWhile' }
|
103
|
+
its(:code_item) { should be_instance_of Android::Dex::DexObject::CodeItem }
|
104
|
+
describe '#parameters' do
|
105
|
+
subject { first_virtual_method.parameters }
|
106
|
+
it { should have(1).item }
|
107
|
+
it { should include('int') }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
context 'about the 12th virtual method(processTryCatch) of the last class' do
|
111
|
+
let(:a_virtual_method) { last_class.virtual_methods[12]}
|
112
|
+
subject { a_virtual_method }
|
113
|
+
its(:name) { should eq 'processTryCatch' }
|
114
|
+
describe '#code_item' do
|
115
|
+
subject { a_virtual_method.code_item }
|
116
|
+
it { should be_instance_of Android::Dex::DexObject::CodeItem }
|
117
|
+
its(:debug_info_item) { should be_instance_of Android::Dex::DexObject::DebugInfoItem }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Android::Dex do
|
4
|
+
|
5
|
+
describe ".uleb128" do
|
6
|
+
# @see http://en.wikipedia.org/wiki/LEB128
|
7
|
+
it "[0x00] should be 0" do
|
8
|
+
Android::Dex.uleb128("\x00").should == [0,1]
|
9
|
+
end
|
10
|
+
it "[0x01] should be 1" do
|
11
|
+
Android::Dex.uleb128("\x01").should == [1,1]
|
12
|
+
end
|
13
|
+
it "[0x7f] should be 127" do
|
14
|
+
Android::Dex.uleb128("\x7f").should == [127,1]
|
15
|
+
end
|
16
|
+
it "[0x80,0x7f] should be 16256" do
|
17
|
+
Android::Dex.uleb128("\x80\x7f").should == [16256,2]
|
18
|
+
end
|
19
|
+
it "[0xe5,0x8e,0x26] should be 624485" do
|
20
|
+
Android::Dex.uleb128("\xe5\x8e\x26").should == [624485,3]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".uleb128p1" do
|
25
|
+
it "[0x00] should be -1" do
|
26
|
+
Android::Dex.uleb128p1("\x00").should == [-1,1]
|
27
|
+
end
|
28
|
+
it "[0x01] should be 0" do
|
29
|
+
Android::Dex.uleb128p1("\x01").should == [0,1]
|
30
|
+
end
|
31
|
+
it "[0x7f] should be 126" do
|
32
|
+
Android::Dex.uleb128p1("\x7f").should == [126,1]
|
33
|
+
end
|
34
|
+
it "[0x80,0x7f] should be 16255" do
|
35
|
+
Android::Dex.uleb128p1("\x80\x7f").should == [16255,2]
|
36
|
+
end
|
37
|
+
it "[0xe5,0x8e,0x26] should be 624485" do
|
38
|
+
Android::Dex.uleb128("\xe5\x8e\x26").should == [624485,3]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
describe '.sleb128' do
|
42
|
+
it "[0x00] should be 0" do
|
43
|
+
Android::Dex.sleb128("\x00").should == [0,1]
|
44
|
+
end
|
45
|
+
it "[0x01] should be 1" do
|
46
|
+
Android::Dex.sleb128("\x01").should == [1,1]
|
47
|
+
end
|
48
|
+
it "[0x7f] should be -1" do
|
49
|
+
Android::Dex.sleb128("\x7f").should == [-1,1]
|
50
|
+
end
|
51
|
+
it "[0x80,0x7f] should be 127" do
|
52
|
+
Android::Dex.sleb128("\x80\x7f").should == [-128,2]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|