ruby_apk 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|