mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-07-15 19:43:28 +01:00
Compare commits
701 Commits
Author | SHA1 | Date | |
---|---|---|---|
e1e5d466a2 | |||
3a863fa143 | |||
e548e75017 | |||
1b0799bcc5 | |||
ea22bc062e | |||
ba6889ea7f | |||
9a9e8395cf | |||
0995e57689 | |||
7c920f953c | |||
e5417e5b7f | |||
d295b7d92d | |||
e33245ac72 | |||
57a8e62be9 | |||
7ce1044eff | |||
5a70ab8534 | |||
486c0c6887 | |||
9e498a911e | |||
3d80e4ef34 | |||
713fcc44d9 | |||
39bc8eae20 | |||
adf13feb81 | |||
b312bf317d | |||
d8a5d2fa37 | |||
a3d042e054 | |||
afe3c16908 | |||
5eee03c689 | |||
6266edad6f | |||
64426601fe | |||
cea39a6193 | |||
c952b2f0d4 | |||
ea05022f5f | |||
cdc7c96cdf | |||
2f683e59d2 | |||
6915de98f1 | |||
27fd2a81d3 | |||
a1c19b55b8 | |||
890f4309d4 | |||
c8cb153f8e | |||
10c1f64216 | |||
f194ef281b | |||
4d607b46c5 | |||
cdd0834447 | |||
4c0d3f8d20 | |||
8dcf1ba6a5 | |||
081358769d | |||
10a614ff04 | |||
58a8ea9051 | |||
335fff2a6f | |||
cd0863d7fa | |||
5cd9bc756c | |||
362e93c4cb | |||
3f7d44de28 | |||
f35c444ddb | |||
e35d0f2959 | |||
6d9a03ad8f | |||
c6cdd8c0e6 | |||
8cf4c259c0 | |||
77bb14331f | |||
dd8843d24a | |||
16b302679a | |||
f9db0a3798 | |||
548eb99bdc | |||
f4669e2b20 | |||
0578f26bc7 | |||
12230da959 | |||
afa2e307cc | |||
d662498ea8 | |||
9e934c6733 | |||
596595698d | |||
220e0962e1 | |||
be3c91b131 | |||
efad084b24 | |||
6da0550b98 | |||
9500bcd914 | |||
7f8e7fed4b | |||
87972e2605 | |||
33874a8f71 | |||
93dba8b35d | |||
6f629601be | |||
565f352114 | |||
920d80ad6e | |||
9962b413d7 | |||
fdff80a72b | |||
6cc4626f50 | |||
665d76456b | |||
f4b91b6c9e | |||
4730ac2989 | |||
6737fda24e | |||
f72bf95612 | |||
2a07ed92fe | |||
a4c7340746 | |||
6eb4e59129 | |||
d65561e3e9 | |||
64d3ddc8e6 | |||
579b940d75 | |||
0656e61bc2 | |||
13e787b5d0 | |||
4d7ef408c9 | |||
a533680e49 | |||
4ddb6f44fc | |||
1238cb71f0 | |||
53203bfb50 | |||
11859af894 | |||
1f5f9cf478 | |||
52a07160b9 | |||
0d8204121d | |||
a47d6c6521 | |||
d391b28d2e | |||
e42099851b | |||
0c0ccb10d9 | |||
486494f1f2 | |||
919a44baec | |||
4f09c3118a | |||
b6b1b259dc | |||
f1b63b239c | |||
797cb3c9ea | |||
9bd745b347 | |||
9f3ecd3715 | |||
1a177dd52f | |||
4010618631 | |||
1168c1ada8 | |||
8b7db92ab8 | |||
088102a81d | |||
87ce1fd0ae | |||
4fac39ada9 | |||
81fa5fdf81 | |||
2702731532 | |||
b95ea60213 | |||
8acebe12bd | |||
2172db5833 | |||
ff67ed1697 | |||
ae1a5019dc | |||
51db53d979 | |||
42a4831092 | |||
8e94b79ed8 | |||
37d99e4a5d | |||
0683427acd | |||
64574b1d4c | |||
3653e1b507 | |||
e62262dbb3 | |||
18e47c89c5 | |||
389a1ecf76 | |||
260e9563b3 | |||
48d0e1196c | |||
b64c615e8e | |||
a4e27128e5 | |||
242cf7e33a | |||
cc641e8dac | |||
bb17590689 | |||
4ce38fcd55 | |||
bf694ffdf1 | |||
b36e0061e1 | |||
526ad15c01 | |||
0694ab6792 | |||
2fd7614d97 | |||
c75c102cd2 | |||
c86ce64408 | |||
d6909c5e6a | |||
55e140f20a | |||
0c7a7e4dca | |||
a7ed00d9ed | |||
89aa3e1208 | |||
f33fd97705 | |||
bf598d1873 | |||
c094a47f12 | |||
6dab2b48ab | |||
1809def83f | |||
1158c62c55 | |||
d13defe242 | |||
a9c5ab9bc8 | |||
27af97b795 | |||
a0a189044e | |||
eca6c10a75 | |||
01efbe1807 | |||
81f7f92b84 | |||
95c98fd458 | |||
428abfb6a6 | |||
a8bf8458ec | |||
862eeadb39 | |||
ec66e1d166 | |||
3e4ffa8cd5 | |||
dc14e6ed11 | |||
47ccae1a6b | |||
a2eef179ef | |||
7eb36b959b | |||
b5563113b1 | |||
5720d3f214 | |||
5b82b90939 | |||
bfcb829ab0 | |||
70f646f87d | |||
3f03dec7af | |||
076772da4d | |||
138c95745e | |||
f517e6c4da | |||
8125c3e0a2 | |||
81bdc7251b | |||
b948d28c62 | |||
bc6af25366 | |||
5d8305cfdc | |||
e038fb924a | |||
51c92cb2f5 | |||
e98b653b3e | |||
b1da0fe958 | |||
b10b5970c3 | |||
e866cfed12 | |||
f5b40e3d64 | |||
cc3a815cb7 | |||
9ef8bddc4b | |||
a3011accf8 | |||
317da0a1c4 | |||
7c79c24865 | |||
6c5e52ad56 | |||
1019b30174 | |||
50236b0661 | |||
6cfcb89827 | |||
cf0a4d3ff3 | |||
06764dff00 | |||
8f2ff4d2f8 | |||
9a9de99c5f | |||
f52aa0f7e9 | |||
935edcdd01 | |||
869c0bc6bd | |||
57ab97df89 | |||
773bf26ff2 | |||
2822949517 | |||
4dc0aaa724 | |||
35df2fff30 | |||
4abbe7602a | |||
55e40ded1c | |||
441ecfa98c | |||
7130b3a4ab | |||
510abf4666 | |||
70b265dd34 | |||
ddbd94b2a8 | |||
c4025cad66 | |||
38b368eec2 | |||
2e97bcce70 | |||
9b13f65895 | |||
78c1a80a51 | |||
7f85da6633 | |||
f76d9f9a1f | |||
6ffed382cf | |||
6e7087ee88 | |||
96c6f010f8 | |||
8f1206678a | |||
d10e51e30b | |||
bd0a6ac3c8 | |||
9ba30c27df | |||
8f40e6aa40 | |||
8c9ae6db53 | |||
6ac0c20619 | |||
cfa1827445 | |||
821cbaff4e | |||
c7db9a4543 | |||
6f67f00834 | |||
40db6a9933 | |||
543228f7c3 | |||
58b97305d5 | |||
0246302814 | |||
0aee99a79c | |||
5012ab5f1b | |||
b7b2406a25 | |||
cd05c36250 | |||
b169b45e57 | |||
40cc830de1 | |||
1ebe56ca76 | |||
79f58e74b9 | |||
7fe699eb41 | |||
2bd77f0833 | |||
7b7eb05e7f | |||
ae0c9fa60b | |||
75b56e462c | |||
d8438c5ae8 | |||
ca7a1abe78 | |||
bd37973442 | |||
c38dc287ab | |||
3feb702898 | |||
0f57dee6bf | |||
99b46b4630 | |||
4ce20e2d3f | |||
65aa00483b | |||
da7a3276ed | |||
b89d9bbc4b | |||
54bffb45ab | |||
e7a47f602d | |||
490dd404ae | |||
60f52c2187 | |||
fbb9908298 | |||
01fa0a3571 | |||
be2b14a575 | |||
dab2bbb1c7 | |||
340ae72ca2 | |||
e64dc8bc26 | |||
cb48ece77f | |||
8f67b7f94b | |||
fa553ee430 | |||
01c9c88e79 | |||
0c32e39ce0 | |||
1364ec05e8 | |||
d5c888cc90 | |||
d6ab68bffc | |||
30e9b553ff | |||
6a3f441064 | |||
13cbe2f059 | |||
53b173c55f | |||
f598c60514 | |||
ceda8e74bf | |||
173c71b867 | |||
d88d35be26 | |||
599452d41f | |||
33dae51536 | |||
f8950dea33 | |||
136d1fef0f | |||
5204383582 | |||
bfa1d8dd62 | |||
b75fdf85d0 | |||
fcbb83f5ac | |||
807003128e | |||
3e4d068eff | |||
a3936afb4c | |||
24000a21df | |||
e5c0ca85f0 | |||
51f07c4473 | |||
ffde7401ef | |||
b4026ae390 | |||
f76c00dd99 | |||
b1e375a676 | |||
1102ba1679 | |||
6d999301f3 | |||
e4fdf0bdb9 | |||
d493b1e790 | |||
47e31765b4 | |||
79faec120e | |||
66dbe7a508 | |||
80a780dcfe | |||
694d51ffb6 | |||
1befe63e45 | |||
27b08bade0 | |||
1477a89ee4 | |||
0dfbbae7b6 | |||
a8a8d21de6 | |||
f467f6f991 | |||
4352e02806 | |||
693d0544a3 | |||
eb239c65d0 | |||
f7e4232eaa | |||
8cbf189029 | |||
6e45e1a039 | |||
b6f770cfc5 | |||
c7de8cabd6 | |||
fd7df36a5a | |||
607187ad5b | |||
b4036c5f15 | |||
b6e077c06b | |||
8e0b793f89 | |||
8b82451230 | |||
64c352fab6 | |||
68697a42a7 | |||
ae4ae3da5e | |||
393abc267f | |||
751970f991 | |||
254e9fff38 | |||
088709f290 | |||
850fcb24ab | |||
ace41d10a5 | |||
cb53fe9ec8 | |||
4e161127e1 | |||
bf43bf93bc | |||
7a19046645 | |||
5db11462be | |||
7bf0e3c344 | |||
d16d8bf62d | |||
c93cc81aac | |||
9491763aa7 | |||
a172c8f624 | |||
33286ba982 | |||
f69e4c5b18 | |||
5b543d2edf | |||
17edb13eb9 | |||
40d281b336 | |||
74ea78aa42 | |||
8edce40301 | |||
642d757066 | |||
969201968e | |||
3f76920fa9 | |||
46b78d35be | |||
ba34b973ac | |||
6d173f2f3f | |||
8b1d2c9fe9 | |||
d3c59e2f74 | |||
fce04d2938 | |||
27df426c0d | |||
f8966bf324 | |||
e076d47a7b | |||
ea798aefb3 | |||
dcf13f8c2c | |||
f99c6f5656 | |||
359d9d3e5f | |||
d56f581a0a | |||
f12cf6d557 | |||
311c4e419f | |||
2becd94381 | |||
1daa7f97c0 | |||
7af5868c22 | |||
2e1ce49170 | |||
23eb357e9e | |||
71c5d23d97 | |||
edfef444fb | |||
3a7a5276e4 | |||
f179b09978 | |||
620fbfdd2a | |||
4213e8e7d1 | |||
9fffa7958a | |||
0f2bc17eca | |||
558e40698b | |||
41b52178bb | |||
8aa1bdc63d | |||
8355fcf886 | |||
fa7d89d734 | |||
4649fa13db | |||
10dd2b304e | |||
93fbb7282a | |||
edc26fe75c | |||
6aecf1b35e | |||
4c5e008609 | |||
5bed658045 | |||
8ac5657993 | |||
fc26daecfc | |||
0232341445 | |||
3b052cc619 | |||
3a9505d54e | |||
4e94ff9ed7 | |||
c47ae5cfcf | |||
ce11b94f28 | |||
765fdd7cbb | |||
77d724efa3 | |||
acb9dd61e7 | |||
cc7684986a | |||
e69aea4e69 | |||
2b6f036d9a | |||
5738d19114 | |||
53ae47bff3 | |||
1c8e18bf36 | |||
5dbf7e7d38 | |||
a2945d58cb | |||
8727fe514a | |||
6465e732fd | |||
f9ec869c7b | |||
1bbd3ef87a | |||
486ade6499 | |||
1a23bd03a2 | |||
5018a1ec94 | |||
fe58245843 | |||
006bf6387f | |||
19569816d3 | |||
1dfbaf4ebe | |||
5b7d61b4b9 | |||
b5dc5b8648 | |||
2202326c02 | |||
8608c3b747 | |||
9afe084f2c | |||
83ab1ac441 | |||
ea1d13c37f | |||
20996e9a58 | |||
3711f7316d | |||
d279cc7453 | |||
f6b8fd3f4b | |||
ff2f88fbd7 | |||
96f4ade874 | |||
ac0256e377 | |||
793af6253f | |||
5ef7d2dd44 | |||
cf8cb5bfab | |||
9376c6875b | |||
a6347f5833 | |||
38a7e01e83 | |||
e18366b3f8 | |||
b9701201a3 | |||
441ba974b7 | |||
a33df50ce8 | |||
52d4635fe8 | |||
14924ec6f4 | |||
3d610788a3 | |||
1986511ae8 | |||
175e7f3cc0 | |||
392a3f1600 | |||
b5c0bdb0eb | |||
502b0ed4b3 | |||
cab9d918ab | |||
e686e89b39 | |||
b510b31052 | |||
5b59d101ef | |||
7713f02252 | |||
0a2afdfd84 | |||
530714c61c | |||
67f418f79f | |||
64860a2d1a | |||
f57dd83d1a | |||
3782a33060 | |||
8e27794124 | |||
9d4aa4983a | |||
b426e00f2f | |||
07d34e5615 | |||
b1ae5a5465 | |||
4ea4bc8631 | |||
fe259dca05 | |||
86f3066f56 | |||
0f579e18b3 | |||
25172fb027 | |||
550a0db61a | |||
73c2609a72 | |||
1ec7961b0e | |||
01f2a5f412 | |||
480a054860 | |||
e9ba9352a6 | |||
0a3ff099c0 | |||
75cc5854bf | |||
77aaa0b849 | |||
0945dd6ba4 | |||
4c94ba43ac | |||
efae2e8c32 | |||
59874b862d | |||
da19859c25 | |||
d87e425c24 | |||
2872080d1a | |||
625a3a39a5 | |||
aa2d187c4d | |||
b80e5dc52e | |||
2208d45bfb | |||
51e4e71931 | |||
0388fa6f36 | |||
ee7c04a568 | |||
9707aa6237 | |||
019ee34c0d | |||
873bdf0bc7 | |||
54c409ce6f | |||
a2d0747b4c | |||
25eac432c9 | |||
dd61f99785 | |||
164f207084 | |||
cb01b0c9a9 | |||
139a0698c9 | |||
259b813a96 | |||
299b28b3c1 | |||
ece33c1d68 | |||
f68cf4e317 | |||
c49c5c4121 | |||
b8d7956d4c | |||
fee872585f | |||
2dd3a2ba4d | |||
662033399f | |||
bb33123b17 | |||
fab6a977aa | |||
25dd6b71f3 | |||
246416d4d2 | |||
1fe037486f | |||
f27b500028 | |||
5a780e8211 | |||
60ca0649ab | |||
dbda128813 | |||
ff7a0626ce | |||
9a94c59605 | |||
d3dd9c849a | |||
12a78ce291 | |||
c8a735e298 | |||
071bf9fba7 | |||
ef919a0fa9 | |||
88b18dda07 | |||
242df842bc | |||
a6355885fc | |||
77a44f11c6 | |||
afeb726d53 | |||
7904e6b562 | |||
224b973ace | |||
8660d0f488 | |||
be7aa3d379 | |||
8503fea0ee | |||
4a15a41cf8 | |||
3a90309383 | |||
b48e5ce58a | |||
f33d6f4729 | |||
6f8989a8ba | |||
a826b661f4 | |||
43f4e52995 | |||
23b3b165d5 | |||
2f87e126f0 | |||
59d74b6273 | |||
7b92f355c8 | |||
982069be32 | |||
63ff8987ea | |||
f276d4e39f | |||
1811a8b733 | |||
0ae03e2c54 | |||
c423a8b4bc | |||
c207a34872 | |||
2cb40d3da6 | |||
18d1f9f649 | |||
17ce8d0fe9 | |||
ac03c9bab4 | |||
8bdffe6f9c | |||
2ff13089fd | |||
772346507c | |||
0fc88a84be | |||
6e4f6af942 | |||
c87daa510e | |||
5e1c9694e7 | |||
a9a42164a3 | |||
0d50fe9b77 | |||
e5c228bab2 | |||
7ccac87b93 | |||
24a2afb5b9 | |||
9652801cce | |||
881b7514e2 | |||
17fe6c9a5b | |||
f02b6d5fd9 | |||
eaf4d02aea | |||
56a4d52995 | |||
ec5c149df5 | |||
c0f32237e3 | |||
5a1c8c7a7e | |||
46cd26e774 | |||
544c498eb6 | |||
5ad75dd0b8 | |||
b2248413b7 | |||
9296bafbd9 | |||
8abf39762d | |||
87cbce4244 | |||
ef61f16896 | |||
e96450d226 | |||
2cf08cf448 | |||
59cfd7c757 | |||
d3c7f11f2d | |||
187fd70077 | |||
fe7f98a98b | |||
66c18fcd31 | |||
5773da0d08 | |||
d581f1f329 | |||
f165969d61 | |||
8dc24bd327 | |||
59066cb46d | |||
6c4d88ff57 | |||
a40542d57b | |||
697aefc7bb | |||
8bc71bb810 | |||
91210f26e9 | |||
44a49db04d | |||
0bfa4bff3c | |||
73aa590056 | |||
985b249a24 | |||
f5e138bed0 | |||
b6c0e2e4fd | |||
df8ef6be6b | |||
8a3186e1c8 | |||
68043f2a52 | |||
95bbce77a2 | |||
ec85f9f8a0 | |||
82e4998092 | |||
48259d872b | |||
8d13e1f341 | |||
33ef949507 | |||
68714e0e55 | |||
9ee1666a76 | |||
8dcdc9afe1 | |||
724f6e590e | |||
507090515b | |||
1dfbe9e44c | |||
d303ab2b50 | |||
b17ae78d6b | |||
391b0b01fc | |||
20861f0ee4 | |||
ff5f48b7e7 | |||
9a301175b0 | |||
712c79020d | |||
12dfbef76b | |||
b1f607ef70 | |||
107e8414bb | |||
4f8b7e9f59 | |||
a077e7df3c | |||
a2257fe1e2 | |||
50353d0b8f | |||
0f5621ff66 | |||
2eca77fb02 | |||
3de5b5fe0b | |||
499a9f4082 | |||
3043506d86 | |||
7db904b359 | |||
5abeb7aac2 | |||
e04691afb9 | |||
15ced50640 | |||
1a2e1fdf75 | |||
3531dd6d07 | |||
cf55f317f8 | |||
79554a2dbc | |||
06c232545a | |||
11184750ec | |||
77b221fc5a | |||
20cd6a9c18 | |||
34d7e7055a | |||
0c1e01cad4 | |||
a68e46eb0a | |||
203a3f7d07 |
.gitignoreREADME.rst
dev_scripts
doc
setup.pywlauto
__init__.pyagenda-example-biglittle.yaml
commands
common
config_example.pycore
__init__.pybootstrap.pyconfiguration.pydevice.pyentry_point.pyexecution.pyextension.pyexttype.pyinstrumentation.pyresolver.pyresource.pyresult.pysignal.pyversion.pyworkload.py
devices
external
daq_server
src
daqpower
revent
sqlite
uiauto
app
build.gradlebuild.shbuild.xmlgradle
gradlewgradlew.batproject.propertiessettings.gradlesrc
com
arm
wlauto
uiauto
instrumentation
__init__.py
acmecape
daq
delay
dmesg
energy_model
energy_probe
fps
freqsweep
hwmon
juno_energy
misc
netstats
perf
poller
screenon
servo_power_monitors
trace_cmd
modules
resource_getters
result_processors
tests
tools
utils
__init__.pyandroid.pycli.pycros_sdk.pydoc.pyfps.pyhwmon.pyipython.pymisc.pypower.pyrevent.pyserial_port.pyssh.pystatedetect.pyterminalsize.pytrace_cmd.pytypes.pyuboot.pyuefi.pyuxperf.py
workloads
__init__.py
adobereader
__init__.pycom.arm.wlauto.uiauto.adobereader.apk
uiauto
andebench
__init__.pycom.arm.wlauto.uiauto.andebench.apkcom.arm.wlauto.uiauto.andebench.jar
uiauto
androbench
com.arm.wlauto.uiauto.androbench.apkcom.arm.wlauto.uiauto.androbench.jar
uiauto
app
build.gradlebuild.shbuild.xmlgradle
gradlewgradlew.batproject.propertiessettings.gradlesrc
com
arm
wlauto
uiauto
angrybirds
angrybirds_rio
anomaly2
antutu
__init__.pycom.arm.wlauto.uiauto.antutu.apkcom.arm.wlauto.uiauto.antutu.jar
uiauto
apklaunch
applaunch
__init__.pycom.arm.wlauto.uiauto.applaunch.apkdevice_script.template
uiauto
appshare
__init__.pycom.arm.wlauto.uiauto.appshare.apk
uiauto
audio
autotest
bbench
benchmarkpi
com.arm.wlauto.uiauto.benchmarkpi.apkcom.arm.wlauto.uiauto.benchmarkpi.jar
uiauto
blogbench
caffeinemark
com.arm.wlauto.uiauto.caffeinemark.apkcom.arm.wlauto.uiauto.caffeinemark.jar
uiauto
cameracapture
__init__.pycom.arm.wlauto.uiauto.cameracapture.apkcom.arm.wlauto.uiauto.cameracapture.jar
uiauto
app
build.gradlebuild.shbuild.xmlgradle
gradlewgradlew.batproject.propertiessettings.gradlesrc
com
arm
wlauto
uiauto
camerarecord
__init__.pycom.arm.wlauto.uiauto.camerarecord.apkcom.arm.wlauto.uiauto.camerarecord.jar
uiauto
app
build.gradlebuild.shbuild.xmlgradle
gradlewgradlew.batproject.propertiessettings.gradlesrc
com
arm
wlauto
uiauto
castlemaster
cfbench
__init__.pycom.arm.wlauto.uiauto.cfbench.apkcom.arm.wlauto.uiauto.cfbench.jar
uiauto
citadel
cyclictest
dex2oat
dhrystone
facebook
__init__.pycom.arm.wlauto.uiauto.facebook.apkcom.arm.wlauto.uiauto.facebook.jar
uiauto
geekbench
__init__.pycom.arm.wlauto.uiauto.geekbench.apkcom.arm.wlauto.uiauto.geekbench.jar
uiauto
app
build.gradlebuild.shbuild.xmlgradle
gradlewgradlew.batproject.propertiessettings.gradlesrc
com
arm
wlauto
uiauto
glbcorp
glbenchmark
__init__.pycom.arm.wlauto.uiauto.glb.jarcom.arm.wlauto.uiauto.glbenchmark.apk
uiauto
gmail
__init__.pycom.arm.wlauto.uiauto.gmail.apk
uiauto
googlemap
googlephotos
__init__.pycom.arm.wlauto.uiauto.googlephotos.apk
uiauto
googleplaybooks
__init__.pycom.arm.wlauto.uiauto.googleplaybooks.apk
uiauto
googleslides
__init__.pycom.arm.wlauto.uiauto.googleslides.apk
uiauto
gunbros2
homescreen
hwuitest
idle
linpack
com.arm.wlauto.uiauto.linpack.apkcom.arm.wlauto.uiauto.linpack.jar
uiauto
lmbench
manual
nenamark
octaned8
peacekeeper
__init__.pycom.arm.wlauto.uiauto.peacekeeper.apkcom.arm.wlauto.uiauto.peacekeeper.jar
uiauto
app
build.gradlebuild.shbuild.xmlgradle
gradlewgradlew.batproject.propertiessettings.gradlesrc
com
arm
wlauto
uiauto
power_loadtest
quadrant
__init__.pycom.arm.wlauto.uiauto.quadrant.apkcom.arm.wlauto.uiauto.quadrant.jar
uiauto
real_linpack
com.arm.wlauto.uiauto.reallinpack.apkcom.arm.wlauto.uiauto.reallinpack.jar
uiauto
realracing3
recentfling
rt_app
skype
__init__.pycom.arm.wlauto.uiauto.skype.apk
uiauto
skypevideo
smartbench
com.arm.wlauto.uiauto.smartbench.apkcom.arm.wlauto.uiauto.smartbench.jar
uiauto
spec2000
sqlite
__init__.pycom.arm.wlauto.uiauto.sqlite.apkcom.arm.wlauto.uiauto.sqlite.jar
uiauto
stress_ng
sysbench
thechase
vellamo
__init__.pycom.arm.wlauto.uiauto.vellamo.apkcom.arm.wlauto.uiauto.vellamo.jar
uiauto
video
videostreaming
__init__.pycom.arm.wlauto.uiauto.videostreaming.apkcom.arm.wlauto.uiauto.videostreaming.jar
uiauto
youtube
17
.gitignore
vendored
17
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
*.bak
|
||||
*.o
|
||||
*.cmd
|
||||
*.iml
|
||||
Module.symvers
|
||||
modules.order
|
||||
*~
|
||||
@ -14,9 +15,12 @@ wa_output/
|
||||
doc/source/api/
|
||||
doc/source/extensions/
|
||||
MANIFEST
|
||||
wlauto/external/uiautomator/bin/
|
||||
wlauto/external/uiautomator/*.properties
|
||||
wlauto/external/uiautomator/build.xml
|
||||
wlauto/external/uiauto/**/build/
|
||||
wlauto/external/uiauto/*.properties
|
||||
wlauto/external/uiauto/app/libs/
|
||||
wlauto/external/uiauto/app/proguard-rules.pro
|
||||
wlauto/external/uiauto/**/.gradle
|
||||
wlauto/external/uiauto/**/.idea
|
||||
*.orig
|
||||
local.properties
|
||||
wlauto/external/revent/libs/
|
||||
@ -27,4 +31,9 @@ pmu_logger.mod.c
|
||||
.tmp_versions
|
||||
obj/
|
||||
libs/armeabi
|
||||
wlauto/workloads/*/uiauto/bin/
|
||||
wlauto/workloads/*/uiauto/**/build/
|
||||
wlauto/workloads/*/uiauto/*.properties
|
||||
wlauto/workloads/*/uiauto/app/libs/
|
||||
wlauto/workloads/*/uiauto/app/proguard-rules.pro
|
||||
wlauto/workloads/*/uiauto/**/.gradle
|
||||
wlauto/workloads/*/uiauto/**/.idea
|
||||
|
16
README.rst
16
README.rst
@ -1,6 +1,19 @@
|
||||
Workload Automation
|
||||
+++++++++++++++++++
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<span style="border: solid red 3px">
|
||||
<p align="center">
|
||||
<font color="red">DO NOT USE "MASTER" BRANCH</font>. WA2 has been
|
||||
deprecated in favor of WA3, which is currently on "next" branch.
|
||||
</p>
|
||||
<p align="center">
|
||||
The master branch will be aligned with next shortly. In the mean time,
|
||||
please <font style="bold">USE "NEXT" BRANCH</font> instead.
|
||||
</p>
|
||||
</span>
|
||||
|
||||
Workload Automation (WA) is a framework for executing workloads and collecting
|
||||
measurements on Android and Linux devices. WA includes automation for nearly 50
|
||||
workloads (mostly Android), some common instrumentation (ftrace, ARM
|
||||
@ -46,7 +59,8 @@ documentation.
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can view pre-built HTML documentation `here <http://pythonhosted.org/wlauto/>`_.
|
||||
You can view pre-built HTML documentation
|
||||
`here <http://workload-automation.readthedocs.io/en/latest/>`_.
|
||||
|
||||
Documentation in reStructuredText format may be found under ``doc/source``. To
|
||||
compile it into cross-linked HTML, make sure you have `Sphinx
|
||||
|
@ -6,6 +6,11 @@ distributed as part of WA releases.
|
||||
Scripts
|
||||
-------
|
||||
|
||||
:check_apk_versions: Compares WA workload versions with the versions listed in APK
|
||||
if there are any incistency it will highlight these. This
|
||||
requires all APK files to be present for workloads with
|
||||
versions.
|
||||
|
||||
:clean_install: Performs a clean install of WA from source. This will remove any
|
||||
existing WA install (regardless of whether it was made from
|
||||
source or through a tarball with pip).
|
||||
|
66
dev_scripts/check_apk_versions
Normal file
66
dev_scripts/check_apk_versions
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
from wlauto.core.extension_loader import ExtensionLoader
|
||||
from wlauto.common.android.workload import ApkWorkload
|
||||
from wlauto.utils.android import ApkInfo
|
||||
|
||||
el = ExtensionLoader()
|
||||
|
||||
|
||||
class fake_config(object):
|
||||
def __init__(self, ext_loader):
|
||||
self.ext_loader = ext_loader
|
||||
self.get_extension = ext_loader.get_extension
|
||||
|
||||
|
||||
class fake_device(object):
|
||||
platform = "android"
|
||||
|
||||
config = fake_config(el)
|
||||
device = fake_device()
|
||||
|
||||
if "WA_USER_DIRECTORY" in os.environ:
|
||||
base_path = os.environ["WA_USER_DIRECTORY"]
|
||||
else:
|
||||
base_path = "~/.workload_automation/dependencies/"
|
||||
|
||||
apk_workloads = [e for e in el.list_workloads()
|
||||
if issubclass(el.get_extension_class(e.name), ApkWorkload)]
|
||||
|
||||
for wl in apk_workloads:
|
||||
# Get versions from workloads
|
||||
workload_versions = []
|
||||
for p in wl.parameters:
|
||||
if p.name == "version" and p.allowed_values:
|
||||
workload_versions = p.allowed_values
|
||||
break
|
||||
else:
|
||||
continue
|
||||
|
||||
dep_path = os.path.join(os.path.expanduser(base_path), wl.name)
|
||||
apks = [apk for apk in os.listdir(dep_path) if apk.endswith(".apk")]
|
||||
|
||||
# Get versions from APK files
|
||||
apk_versions = []
|
||||
for apk in apks:
|
||||
# skip antutu 3d benchmark apk
|
||||
if apk == "com.antutu.benchmark.full-1.apk":
|
||||
continue
|
||||
apk_versions.append(ApkInfo(os.path.join(dep_path, apk)).version_name)
|
||||
|
||||
# Output workload info
|
||||
print "Workload: {}".format(wl.name)
|
||||
print "Workload Versions: {}".format(sorted(workload_versions, key=StrictVersion))
|
||||
print "APK versions: {}".format(sorted(apk_versions, key=StrictVersion))
|
||||
|
||||
# Check for bad/missing versions
|
||||
error = False
|
||||
for v in apk_versions:
|
||||
if v not in workload_versions:
|
||||
msg = "APK version '{}' not present in workload list of versions"
|
||||
print msg.format(v)
|
||||
error = True
|
||||
if not error:
|
||||
print "OK"
|
@ -10,22 +10,29 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from wlauto.exceptions import WAError, ToolError
|
||||
from wlauto.utils.doc import format_simple_table
|
||||
from wlauto.utils.misc import check_output, get_null, which
|
||||
|
||||
|
||||
def get_aapt_path():
|
||||
"""Return the full path to aapt tool."""
|
||||
sdk_path = os.getenv('ANDROID_HOME')
|
||||
if not sdk_path:
|
||||
raise ToolError('Please make sure you have Android SDK installed and have ANDROID_HOME set.')
|
||||
build_tools_directory = os.path.join(sdk_path, 'build-tools')
|
||||
versions = os.listdir(build_tools_directory)
|
||||
for version in reversed(sorted(versions)):
|
||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||
if os.path.isfile(aapt_path):
|
||||
logging.debug('Found aapt for version {}'.format(version))
|
||||
return aapt_path
|
||||
if sdk_path:
|
||||
build_tools_directory = os.path.join(sdk_path, 'build-tools')
|
||||
versions = os.listdir(build_tools_directory)
|
||||
for version in reversed(sorted(versions)):
|
||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||
if os.path.isfile(aapt_path):
|
||||
logging.debug('Found aapt for version {}'.format(version))
|
||||
return aapt_path
|
||||
else:
|
||||
raise ToolError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||
else:
|
||||
raise ToolError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||
logging.debug("ANDROID_HOME is not set, try to find in $PATH.")
|
||||
aapt_path = which('aapt')
|
||||
if aapt_path:
|
||||
logging.debug('Using aapt from {}'.format(aapt_path))
|
||||
return aapt_path
|
||||
raise ToolError('Please make sure you have Android SDK installed and have ANDROID_HOME set.')
|
||||
|
||||
|
||||
def get_apks(path):
|
||||
|
59
doc/Makefile
59
doc/Makefile
@ -7,18 +7,11 @@ SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
SPHINXAPI = sphinx-apidoc
|
||||
SPHINXAPIOPTS =
|
||||
|
||||
WAEXT = ./build_extension_docs.py
|
||||
WAEXTOPTS = source/extensions ../wlauto ../wlauto/external ../wlauto/tests
|
||||
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
ALLSPHINXAPIOPTS = -f $(SPHINXAPIOPTS) -o source/api ../wlauto
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
@ -58,52 +51,38 @@ coverage:
|
||||
@echo
|
||||
@echo "Build finished. The coverage reports are in $(BUILDDIR)/coverage."
|
||||
|
||||
api: ../wlauto
|
||||
rm -rf source/api/*
|
||||
$(SPHINXAPI) $(ALLSPHINXAPIOPTS)
|
||||
|
||||
waext: ../wlauto
|
||||
rm -rf source/extensions
|
||||
mkdir -p source/extensions
|
||||
$(WAEXT) $(WAEXTOPTS)
|
||||
|
||||
|
||||
sigtab: ../wlauto/core/instrumentation.py source/instrumentation_method_map.template
|
||||
rm -rf source/instrumentation_method_map.rst
|
||||
./build_instrumentation_method_map.py source/instrumentation_method_map.rst
|
||||
|
||||
html: api waext sigtab
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml: api waext sigtab
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml: api waext sigtab
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle: api waext sigtab
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json: api waext sigtab
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp: api waext sigtab
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp: api waext sigtab
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
@ -112,7 +91,7 @@ qthelp: api waext sigtab
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WorkloadAutomation2.qhc"
|
||||
|
||||
devhelp: api
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@ -121,64 +100,64 @@ devhelp: api
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WorkloadAutomation2"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub: api waext sigtab
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex: api waext sigtab
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf: api waext sigtab
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text: api waext sigtab
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man: api waext sigtab
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo: api waext sigtab
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info: api waext sigtab
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext: api waext sigtab
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes: api waext sigtab
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck: api waext sigtab
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest: api waext sigtab
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
from wlauto import ExtensionLoader
|
||||
from wlauto.utils.doc import get_rst_from_extension, underline
|
||||
@ -30,6 +31,9 @@ def generate_extension_documentation(source_dir, outdir, ignore_paths):
|
||||
loader = ExtensionLoader(keep_going=True)
|
||||
loader.clear()
|
||||
loader.update(paths=[source_dir], ignore_paths=ignore_paths)
|
||||
if os.path.exists(outdir):
|
||||
shutil.rmtree(outdir)
|
||||
os.makedirs(outdir)
|
||||
for ext_type in loader.extension_kinds:
|
||||
if not ext_type in GENERATE_FOR:
|
||||
continue
|
||||
|
@ -16,7 +16,6 @@
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
from copy import copy
|
||||
|
||||
from wlauto.core.instrumentation import SIGNAL_MAP, PRIORITY_MAP
|
||||
from wlauto.utils.doc import format_simple_table
|
||||
@ -25,7 +24,7 @@ from wlauto.utils.doc import format_simple_table
|
||||
CONVINIENCE_ALIASES = ['initialize', 'setup', 'start', 'stop', 'process_workload_result',
|
||||
'update_result', 'teardown', 'finalize']
|
||||
|
||||
OUTPUT_TEMPLATE_FILE = os.path.join(os.path.dirname(__file__), 'source', 'instrumentation_method_map.template')
|
||||
OUTPUT_TEMPLATE_FILE = os.path.join(os.path.dirname(__file__), 'source', 'instrumentation_method_map.template')
|
||||
|
||||
|
||||
def escape_trailing_underscore(value):
|
||||
@ -37,7 +36,9 @@ def generate_instrumentation_method_map(outfile):
|
||||
signal_table = format_simple_table([(k, v) for k, v in SIGNAL_MAP.iteritems()],
|
||||
headers=['method name', 'signal'], align='<<')
|
||||
priority_table = format_simple_table([(escape_trailing_underscore(k), v) for k, v in PRIORITY_MAP.iteritems()],
|
||||
headers=['prefix', 'priority'], align='<>')
|
||||
headers=['prefix', 'priority'], align='<>')
|
||||
if os.path.isfile(outfile):
|
||||
os.unlink(outfile)
|
||||
with open(OUTPUT_TEMPLATE_FILE) as fh:
|
||||
template = string.Template(fh.read())
|
||||
with open(outfile, 'w') as wfh:
|
||||
|
2
doc/requirements.txt
Normal file
2
doc/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
nose
|
||||
sphinx==1.6.5
|
@ -147,7 +147,7 @@ to 15 million. You can specify this using dhrystone's parameters:
|
||||
|
||||
wa show dhrystone
|
||||
|
||||
see the :ref:`Invocation` section for details.
|
||||
see the :ref:`Invocation <invocation>` section for details.
|
||||
|
||||
In addition to configuring the workload itself, we can also specify
|
||||
configuration for the underlying device. This can be done by setting runtime
|
||||
|
57
doc/source/apk_workloads.rst
Normal file
57
doc/source/apk_workloads.rst
Normal file
@ -0,0 +1,57 @@
|
||||
.. _apk_workload_settings:
|
||||
|
||||
APK Workloads
|
||||
=============
|
||||
|
||||
APK resolution
|
||||
--------------
|
||||
|
||||
WA has various resource getters that can be configured to locate APK files but for most people APK files
|
||||
should be kept in the ``$WA_USER_DIRECTORY/dependencies/SOME_WORKLOAD/`` directory. (by default
|
||||
``~/.workload_automation/dependencies/SOME_WORKLOAD/``). The ``WA_USER_DIRECTORY`` enviroment variable can be used
|
||||
to chnage the location of this folder. The APK files need to be put into the corresponding directories
|
||||
for the workload they belong to. The name of the file can be anything but as explained below may need
|
||||
to contain certain peices of information.
|
||||
|
||||
All ApkWorkloads have parameters that affect the way in which APK files are resolved, ``exact_abi``,
|
||||
``force_install`` and ``check_apk``. Their exact behaviours are outlined below.
|
||||
|
||||
.. confval:: exact_abi
|
||||
|
||||
If this setting is enabled WA's resource resolvers will look for the devices ABI with any native
|
||||
code present in the apk. By default this setting is disabled since most apks will work across all
|
||||
devices. You may wish to enable this feature when working with devices that support multiple ABI's (like
|
||||
64-bit devices that can run 32-bit APK files) and are specifically trying to test one or the other.
|
||||
|
||||
.. confval:: force_install
|
||||
|
||||
If this setting is enabled WA will *always* use the APK file on the host, and re-install it on every
|
||||
iteration. If there is no APK on the host that is a suitable version and/or ABI for the workload WA
|
||||
will error when ``force_install`` is enabled.
|
||||
|
||||
.. confval:: check_apk
|
||||
|
||||
This parameter is used to specify a preference over host or target versions of the app. When set to
|
||||
``True`` WA will prefer the host side version of the APK. It will check if the host has the APK and
|
||||
if the host APK meets the version requirements of the workload. If does and the target already has
|
||||
same version nothing will be done, other wise it will overwrite the targets app with the host version.
|
||||
If the hosts is missing the APK or it does not meet version requirements WA will fall back to the app
|
||||
on the target if it has the app and it is of a suitable version. When this parameter is set to
|
||||
``false`` WA will prefer to use the version already on the target if it meets the workloads version
|
||||
requirements. If it does not it will fall back to search the host for the correct version. In both modes
|
||||
if neither the host nor target have a suitable version, WA will error and not run the workload.
|
||||
|
||||
Some workloads will also feature the follow parameters which will alter the way their APK files are resolved.
|
||||
|
||||
.. confval:: version
|
||||
|
||||
This parameter is used to specify which version of uiautomation for the workload is used. In some workloads
|
||||
e.g. ``geekbench`` multiple versions with drastically different UI's are supported. When a workload uses a
|
||||
version it is required for the APK file to contain the uiautomation version in the file name. In the case
|
||||
of antutu the file names could be: ``geekbench_2.apk`` or ``geekbench_3.apk``.
|
||||
|
||||
.. confval:: variant_name
|
||||
|
||||
Some workloads use variants of APK files, this is usually the case with web browser APK files, these work
|
||||
in exactly the same way as the version, the variant of the apk
|
||||
|
@ -1,6 +1,454 @@
|
||||
=================================
|
||||
What's New in Workload Automation
|
||||
=================================
|
||||
-------------
|
||||
Version 2.6.0
|
||||
-------------
|
||||
|
||||
.. note:: Users who are currently using the GitHub master version of WA should
|
||||
uninstall the existing version before upgrading to avoid potential issues.
|
||||
|
||||
Additions:
|
||||
##########
|
||||
|
||||
Workloads
|
||||
~~~~~~~~~
|
||||
- ``AdobeReader``: A workload that carries out following typical productivity
|
||||
tasks. These include opening a file, performing various gestures and
|
||||
zooms on screen and searching for a predefined set of strings.
|
||||
- ``octaned8``: A workload to run the binary (non-browser) version of the JS
|
||||
benchmark Octane.
|
||||
- ``GooglePlayBooks``: A workload to perform standard productivity tasks with
|
||||
Google Play Books. This workload performs various tasks, such as searching
|
||||
for a book title online, browsing through a book, adding and removing notes,
|
||||
word searching, and querying information about the book.
|
||||
- ``GooglePhotos``: A workload to perform standard productivity tasks with
|
||||
Google Photos. Carries out various tasks, such as browsing images,
|
||||
performing zooms, and post-processing the image.
|
||||
- ``GoogleSlides``: Carries out various tasks, such as creating a new
|
||||
presentation, adding text, images, and shapes, as well as basic editing and
|
||||
playing a slideshow.
|
||||
- ``Youtube``: The workload plays a video, determined by the ``video_source``
|
||||
parameter. While the video is playing, some common actions such as video
|
||||
seeking, pausing playback and navigating the comments section are performed.
|
||||
- ``Skype``: Replacement for the ``skypevideo`` workload. Logs into Skype
|
||||
and initiates a voice or video call with a contact.
|
||||
|
||||
Framework
|
||||
~~~~~~~~~
|
||||
- ``AndroidUxPerfWorkload``: Added a new workload class to encapsulate
|
||||
functionality common to all uxperf workloads.
|
||||
- ``UxPerfUiAutomation``: Added class which contains methods specific to
|
||||
UX performance
|
||||
testing.
|
||||
- ``get-assets``: Added new script and command to retrieve external assets
|
||||
for workloads
|
||||
|
||||
Results Processors
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
- ``uxperf``: Parses device logcat for `UX_PERF` markers to produce performance
|
||||
metrics for workload actions using specified instrumentation.
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
- ``State Detection``: Added feature to use visual state detection to
|
||||
verify the state of a workload after setup and run.
|
||||
|
||||
|
||||
Fixes/Improvements:
|
||||
###################
|
||||
|
||||
Documentation
|
||||
~~~~~~~~~~~~~~
|
||||
- ``Revent``: Added file structure to the documentation.
|
||||
- Clarified documentation regarding binary dependencies.
|
||||
- Updated documentation with ``create`` and ``get-assets`` commands.
|
||||
|
||||
Instruments
|
||||
~~~~~~~~~~~~
|
||||
- ``sysfs_extractor``: Fixed error when `tar.gz` file already existed on device,
|
||||
now overwrites.
|
||||
- ``cpufreq``: Fixed error when `tar.gz` file already existed on device, now
|
||||
overwrites.
|
||||
- ``file-poller``:
|
||||
- Improved csv output.
|
||||
- Added error checking and reporting.
|
||||
- Changed ``files`` to be a mandatory parameter.
|
||||
- ``fps``:
|
||||
- Added a new parameter to fps instrument to specify the time period between
|
||||
calls to ``dumpsys SurfaceFlinger --latency`` when collecting frame data.
|
||||
- Added gfxinfo methods to obtain fps stats. Auto detects and uses appropriate
|
||||
method via android version of device.
|
||||
- Fixed issue with regex.
|
||||
- Now handles empty frames correctly.
|
||||
- ``energy_model``: Ensures that the ``ui`` runtime parameter is only set for
|
||||
ChromeOS devices.
|
||||
- ``ftrace``: Added support to handle traces collected by both WA and devlib.
|
||||
- ``Perf``: Updated 32bit binary file for little endian devices.
|
||||
|
||||
Resource Getters
|
||||
~~~~~~~~~~~~~~~~
|
||||
- ``http_getter``: Now used to try and find executables files from a
|
||||
provided ``remove_assets_url``.
|
||||
|
||||
Result Processors
|
||||
~~~~~~~~~~~~~~~~~
|
||||
- ``cpu_states``: Fixes using stand-alone script with timeline option.
|
||||
|
||||
Workloads
|
||||
~~~~~~~~~
|
||||
- ``antutu``: Fixed setting permissions of ``FINE_LOCATION`` on some devices.
|
||||
- ``bbench`` Fixed handling of missing results.
|
||||
- ``camerarecord``:
|
||||
- Added frame stats collection through dumpsys gfxinfo.
|
||||
- Added possibility to select slow_motion recording mode.
|
||||
- ``Geekbench``:
|
||||
- Fixed output file listing causing pull failure.
|
||||
- Added support for Geekbench 4.
|
||||
- ``recentfling``:
|
||||
- Fixed issue when binaries were not uninstalled correctly.
|
||||
- Scripts are now deployed via ``install()`` to ensure they are executable.
|
||||
- Fixed handling of when a PID file is deleted before reaching processing
|
||||
results stage.
|
||||
- Added parameter to not start any apps before flinging.
|
||||
- ``rt-app``: Added camera recorder simulation.
|
||||
- ``sysbench``: Added arm64 binary.
|
||||
- ``Vellamo``: Fixed capitalization in part of UIAutomation to prevent
|
||||
potential issues.
|
||||
- ``Spec2000``: Now uses WA deployed version of busybox.
|
||||
- ``NetStat``: Updated to support new default logcat format in Android 6.
|
||||
- ``Dex2oat``: Now uses root if available.
|
||||
|
||||
Framework
|
||||
~~~~~~~~~
|
||||
- ``adb_shell``:
|
||||
- Fixed issue when using single quoted command with ``adb_shell``.
|
||||
- Correctly forward stderror to the caller for newer version of adb.
|
||||
- ``revent``
|
||||
- Added ``-S`` argument to "record" command to automatically record a
|
||||
screen capture after a recording is completed.
|
||||
- Fixed issue with multiple iterations of a revent workload.
|
||||
- Added ``-s`` option to executable to allow waiting on stdin.
|
||||
- Removed timeout in command as ``-s`` is specified.
|
||||
- Revent recordings can now be parsed and used within WA.
|
||||
- Fixed issue when some recordings wouldn't be retrieved correctly.
|
||||
- Timeout is now based on recording duration.
|
||||
- Added `magic` and file version to revent files. Revent files should now
|
||||
start with ``REVENT`` followed by the file format version.
|
||||
- Added support for gamepad recording. This type of recording contains
|
||||
only the events from a gamepad device (which is automatically
|
||||
identified).
|
||||
- A ``mode`` field has been added to the recording format to help
|
||||
distinguish between the normal and gamepad recording types.
|
||||
- Added ``-g`` option to ``record`` command to expose the gamepad recording
|
||||
mode.
|
||||
- The structure of revent code has undergone a major overhaul to improve
|
||||
maintainability and robustness.
|
||||
- More detailed ``info`` command output.
|
||||
- Updated Makefile to support debug/production builds.
|
||||
- ``Android API``: Upgraded Android API level from 17 to 18.
|
||||
- ``uiautomator``: The window hierarchy is now dumped to a file when WA fails
|
||||
on android devices.
|
||||
- ``AndroidDevice``:
|
||||
- Added support for downgrading when installing an APK.
|
||||
- Added a ``broadcast_media_mounted`` method to force a re-index of the
|
||||
mediaserver cache for a specified directory.
|
||||
- Now correctly handles ``None`` output for ``get_pids_of()`` when there are no
|
||||
running processes with the specified name.
|
||||
- Renamed the capture method from ``capture_view_hierachy`` to
|
||||
``capture_ui_hierarchy``.
|
||||
- Changed the file extension of the capture file to ``.uix``
|
||||
- Added ``-rf`` to delete_files to be consistent with ``LinuxDevice``.
|
||||
- ``LinuxDevice``: Now ensures output from both stdout and etderr is propagated in
|
||||
the event of a DeviceError.
|
||||
- ``APKWorkload``:
|
||||
- Now ensure APKs are replaced properly when reinstalling.
|
||||
- Now checks APK version and ABI when installing.
|
||||
- Fixed error on some devices when trying to grant permissions that were
|
||||
already granted.
|
||||
- Fixed some permissions not being granted.
|
||||
- Now allows disabling the main activity launch in setup (required for some
|
||||
apps).
|
||||
- Added parameter to clear data on reset (default behaviour unchanged).
|
||||
- Ignores exception for non-fatal permission grant failure.
|
||||
- Fixed issue of multiple versions of the same workload failing to find their APK.
|
||||
- Added method to ensure a valid apk version is used within a workload.
|
||||
- Updated how APK resolution is performed to maximise likelihood of
|
||||
a workload running.
|
||||
- When ``check_apk`` is ``True`` will prefer host APK and if no suitable APK
|
||||
is found, will use target APK if the correct version is present. When ``False``
|
||||
will prefer target apk if it is a valid version otherwise will fallback to
|
||||
host APK.
|
||||
- ``RunConfiguration``: Fixed disabling of instruments in workload specs.
|
||||
- ``Devices``:
|
||||
- Added network connectivity check for devices.
|
||||
- Subclasses can now set ``requires_network`` to ``True`` and network
|
||||
connectivity check will be performed during ``setup()``.
|
||||
- ``Workloads``:
|
||||
- Added network check methods.
|
||||
- Fixed versions to be backwards compatible.
|
||||
- Updated workload versions to match APK files.
|
||||
- Fixed issues with calling super.
|
||||
- ``Assets``: Added script to retrieve external assets for workloads.
|
||||
- ``Execution``: Added a ``clean_up`` global config option to delete WA files from
|
||||
devices.
|
||||
- ``Runner``: No longer takes a screenshot or dump of UI hierarchy for some errors when
|
||||
unnecessary, e.g. host errors.
|
||||
- ``core``: Constraints and allowed values are now checked when set instead of
|
||||
when validating.
|
||||
- ``FpsProcessor``:
|
||||
- Added requirement on ``filtered_vsyncs_to_compose`` for ``total_vsync metric``.
|
||||
- Removed misleading comment in class description.
|
||||
- ``BaseUiAutomation``: Added new Marker API so workloads generate start and end
|
||||
markers with a string name.
|
||||
- ``AndroidUiAutoBenchmark``: Automatically checks for known package versions
|
||||
that don't work well with AndroidUiAutoBenchmark workloads.
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
- Updated setup.py url to be a valid URI.
|
||||
- Fixed workload name in big.Little sample agenda.
|
||||
|
||||
Incompatible changes
|
||||
####################
|
||||
|
||||
Framework
|
||||
~~~~~~~~~
|
||||
- ``check_abi``: Now renamed to ``exact_abi``, is used to ensure that if enabled,
|
||||
only an apk containing no native code or code designed for the devices primary
|
||||
abi is use.
|
||||
- ``AndroidDevice``: Renamed ``supported_eabis`` property to ``supported_abis``
|
||||
to be consistent with linux devices.
|
||||
|
||||
Workloads
|
||||
~~~~~~~~~~
|
||||
- ``skypevideo``: Workload removed and replaced with ``skype`` workload.
|
||||
|
||||
-------------
|
||||
Version 2.5.0
|
||||
-------------
|
||||
|
||||
Additions:
|
||||
##########
|
||||
|
||||
Instruments
|
||||
~~~~~~~~~~~
|
||||
- ``servo_power``: Added support for chromebook servo boards.
|
||||
- ``file_poller``: polls files and outputs a CSV of their values over time.
|
||||
- ``systrace``: The Systrace tool helps analyze the performance of your
|
||||
application by capturing and displaying execution times of your applications
|
||||
processes and other Android system processes.
|
||||
|
||||
Workloads
|
||||
~~~~~~~~~
|
||||
- ``blogbench``: Blogbench is a portable filesystem benchmark that tries to
|
||||
reproduce the load of a real-world busy file server.
|
||||
- ``stress-ng``: Designed to exercise various physical subsystems of a computer
|
||||
as well as the various operating system kernel interfaces.
|
||||
- ``hwuitest``: Uses hwuitest from AOSP to test rendering latency on Android
|
||||
devices.
|
||||
- ``recentfling``: Tests UI jank on android devices.
|
||||
- ``apklaunch``: installs and runs an arbitrary apk file.
|
||||
- ``googlemap``: Launches Google Maps and replays previously recorded
|
||||
interactions.
|
||||
|
||||
Framework
|
||||
~~~~~~~~~
|
||||
- ``wlauto.utils.misc``: Added ``memoised`` function decorator that allows
|
||||
caching of previous function/method call results.
|
||||
- Added new ``Device`` APIs:
|
||||
- ``lsmod``: lists kernel modules
|
||||
- ``insmod``: inserts a kernel module from a ``.ko`` file on the host.
|
||||
- ``get_binary_path``: Checks ``binary_directory`` for the wanted binary,
|
||||
if it is not found there it will try to use ``which``
|
||||
- ``install_if_needed``: Will only install a binary if it is not already
|
||||
on the target.
|
||||
- ``get_device_model``: Gets the model of the device.
|
||||
- ``wlauto.core.execution.ExecutionContext``:
|
||||
- ``add_classfiers``: Allows adding a classfier to all metrics for the
|
||||
current result.
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
- Commands:
|
||||
- ``record``: Simplifies recording revent files.
|
||||
- ``replay``: Plays back revent files.
|
||||
|
||||
Fixes/Improvements:
|
||||
###################
|
||||
|
||||
Devices
|
||||
~~~~~~~
|
||||
- ``juno``:
|
||||
- Fixed ``bootargs`` parameter not being passed to ``_boot_via_uboot``.
|
||||
- Removed default ``bootargs``
|
||||
- ``gem5_linux``:
|
||||
- Added ``login_prompt`` and ``login_password_prompt`` parameters.
|
||||
- ``generic_linux``: ABI is now read from the target device.
|
||||
|
||||
Instruments
|
||||
~~~~~~~~~~~
|
||||
- ``trace-cmd``:
|
||||
- Added the ability to report the binary trace on the target device,
|
||||
removing the need for ``trace-cmd`` binary to be present on the host.
|
||||
- Updated to handle messages that the trace for a CPU is empty.
|
||||
- Made timeout for pulling trace 1 minute at minimum.
|
||||
- ``perf``: per-cpu statistics now get added as metrics to the results (with a
|
||||
classifier used to identify the cpu).
|
||||
- ``daq``:
|
||||
- Fixed bug where an exception would be raised if ``merge_channels=False``
|
||||
- No longer allows duplicate channel labels
|
||||
- ``juno_energy``:
|
||||
- Summary metrics are now calculated from the contents of ``energy.csv`` and
|
||||
added to the overall results.
|
||||
- Added a ``strict`` parameter. When this is set to ``False`` the device
|
||||
check during validation is omitted.
|
||||
- ``sysfs_extractor``: tar and gzip are now performed separately to solve
|
||||
permission issues.
|
||||
- ``fps``:
|
||||
- Now only checks for crashed content if ``crash_check`` is ``True``.
|
||||
- Can now process multiple ``view`` attributes.
|
||||
- ``hwmon``: Sensor naming fixed, they are also now added as result classifiers
|
||||
|
||||
Resource Getters
|
||||
~~~~~~~~~~~~~~~~
|
||||
- ``extension_asset``: Now picks up the path to the mounted filer from the
|
||||
``remote_assets_path`` global setting.
|
||||
|
||||
Result Processors
|
||||
~~~~~~~~~~~~~~~~~
|
||||
- ``cpustates``:
|
||||
- Added the ability to configure how a missing ``START`` marker in the trace
|
||||
is handled.
|
||||
- Now raises a warning when there is a ``START`` marker in the trace but no
|
||||
``STOP`` marker.
|
||||
- Exceptions in PowerStateProcessor no longer stop the processing of the
|
||||
rest of the trace.
|
||||
- Now ensures a known initial state by nudging each CPU to bring it out of
|
||||
idle and writing starting CPU frequencies to the trace.
|
||||
- Added the ability to create a CPU utilisation timeline.
|
||||
- Fixed issues with getting frequencies of hotplugged CPUs
|
||||
- ``csv``: Zero-value classifieres are no longer converted to an empty entry.
|
||||
- ``ipynb_exporter``: Default template no longer shows a blank plot for
|
||||
workloads without ``summary_metrics``
|
||||
|
||||
Workloads
|
||||
~~~~~~~~~
|
||||
- ``vellamo``:
|
||||
- Added support for v3.2.4.
|
||||
- Fixed getting values from logcat.
|
||||
- ``cameracapture``: Updated to work with Android M+.
|
||||
- ``camerarecord``: Updated to work with Android M+.
|
||||
- ``lmbench``:
|
||||
- Added the output file as an artifact.
|
||||
- Added taskset support
|
||||
- ``antutu`` - Added support for v6.0.1
|
||||
- ``ebizzy``: Fixed use of ``os.path`` to ``self.device.path``.
|
||||
- ``bbench``: Fixed browser crashes & permissions issues on android M+.
|
||||
- ``geekbench``:
|
||||
- Added check whether device is rooted.
|
||||
- ``manual``: Now only uses logcat on Android devices.
|
||||
- ``applaunch``:
|
||||
- Fixed ``cleanup`` not getting forwarded to script.
|
||||
- Added the ability to stress IO during app launch.
|
||||
- ``dhrystone``: Now uses WA's resource resolution to find it's binary so it
|
||||
uses the correct ABI.
|
||||
- ``glbench``: Updated for new logcat formatting.
|
||||
|
||||
Framework
|
||||
~~~~~~~~~
|
||||
- ``ReventWorkload``:
|
||||
- Now kills all revent instances on teardown.
|
||||
- Device model name is now used when searching for revent files, falling back
|
||||
to WA device name.
|
||||
- ``BaseLinuxDevice``:
|
||||
- ``killall`` will now run as root by default if the device
|
||||
is rooted.
|
||||
- ``list_file_systems`` now handles blank lines.
|
||||
- All binaries are now installed into ``binaries_directory`` this allows..
|
||||
- Busybox is now deployed on non-root devices.
|
||||
- gzipped property files are no zcat'ed
|
||||
- ``LinuxDevice``:
|
||||
- ``kick_off`` no longer requires root.
|
||||
- ``kick_off`` will now run as root by default if the device is rooted.
|
||||
- No longer raises an exception if a connection was dropped during a reboot.
|
||||
- Added a delay before polling for a connection to avoid re-connecting to a
|
||||
device that is still in the process of rebooting.
|
||||
- ``wlauto.utils.types``: ``list_or_string`` now ensures that elements of a list
|
||||
are strings.
|
||||
- ``AndroidDevice``:
|
||||
- ``kick_off`` no longer requires root.
|
||||
- Build props are now gathered via ``getprop`` rather than trying to parse
|
||||
build.prop directly.
|
||||
- WA now pushes its own ``sqlite3`` binary.
|
||||
- Now uses ``content`` instead of ``settings`` to get ``ANDROID_ID``
|
||||
- ``swipe_to_unlock`` parameter is now actually used. It has been changed to
|
||||
take a direction to accomodate various devices.
|
||||
- ``ensure_screen_is_on`` will now also unlock the screen if swipe_to_unlock
|
||||
is set.
|
||||
- Fixed use of variables in as_root=True commands.
|
||||
- ``get_pids_of`` now used ``busybox grep`` since as of Android M+ ps cannot
|
||||
filter by process name anymore.
|
||||
- Fixed installing APK files with whitespace in their path/name.
|
||||
- ``adb_shell``:
|
||||
- Fixed handling of line breaks at the end of command output.
|
||||
- Newline separator is now detected from the target.
|
||||
- As of ADB v1.0.35, ADB returns the return code of the command run. WA now
|
||||
handles this correctly.
|
||||
- ``ApkWorkload``:
|
||||
- Now attempts to grant all runtime permissions for devices on Android M+.
|
||||
- Can now launch packages that don't have a launch activity defined.
|
||||
- Package version is now added to results as a classifier.
|
||||
- Now clears app data if an uninstall failed to ensure it starts from a known
|
||||
state.
|
||||
- ``wlauto.utils.ipython``: Updated to work with ipython v5.
|
||||
- ``Gem5Device``:
|
||||
- Added support for deploying the ``m5`` binary.
|
||||
- No longer waits for the boot animation to finish if it has been disabled.
|
||||
- Fixed runtime error caused by lack of kwargs.
|
||||
- No longer depends on ``busybox``.
|
||||
- Split out commands to resize shell to ``resize_shell``.
|
||||
- Now tries to connect to the shell up to 10 times.
|
||||
- No longer renames gzipped files.
|
||||
- Agendas:
|
||||
- Now errors when an agenda key is empty.
|
||||
- ``wlauto.core.execution.RunInfo``: ``run_name`` will now default to
|
||||
``{output_folder}_{date}_{time}``.
|
||||
- Extensions:
|
||||
- Two different parameters can now have the same global alias as long as they
|
||||
their types match.
|
||||
- You can no longer ``override`` parameters that are defined at the same
|
||||
level.
|
||||
- ``wlauto.core.entry_point``: Now gives a better error when a config file
|
||||
doesn't exist.
|
||||
- ``wlauto.utils.misc``: Added ``aarch64`` to list for arm64 ABI.
|
||||
- ``wlauto.core.resolver``: Now shows what version was being search for when a
|
||||
resource is not found.
|
||||
- Will no longer start instruments ect. if a run has no workload specs.
|
||||
- ``wlauto.utils.uboot``: Now detects uboot version to use correct line endings.
|
||||
- ``wlauto.utils.trace_cmd``: Added a parser for sched_switch events.
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
- Updated to pylint v1.5.1
|
||||
- Rebuilt ``busybox`` binaries to prefer built-in applets over system binaries.
|
||||
- ``BaseUiAutomation``: Added functions for checking version strings.
|
||||
|
||||
Incompatible changes
|
||||
####################
|
||||
|
||||
Instruments
|
||||
~~~~~~~~~~~
|
||||
- ``apk_version``: Removed, use result classifiers instead.
|
||||
|
||||
Framework
|
||||
~~~~~~~~~
|
||||
- ``BaseLinuxDevice``: Removed ``is_installed`` use ``install_if_needed`` and
|
||||
``get_binary_path`` instead.
|
||||
- ``LinuxDevice``: Removed ``has_root`` method, use ``is_rooted`` instead.
|
||||
- ``AndroidDevice``: ``swipe_to_unlock`` method replaced with
|
||||
``perform_unlock_swipe``.
|
||||
|
||||
-------------
|
||||
Version 2.4.0
|
||||
-------------
|
||||
|
@ -28,12 +28,16 @@
|
||||
|
||||
import sys, os
|
||||
import warnings
|
||||
from sphinx.apidoc import main
|
||||
|
||||
warnings.filterwarnings('ignore', "Module louie was already imported")
|
||||
|
||||
this_dir = os.path.dirname(__file__)
|
||||
sys.path.insert(0, os.path.join(this_dir, '..'))
|
||||
sys.path.insert(0, os.path.join(this_dir, '../..'))
|
||||
import wlauto
|
||||
from build_extension_docs import generate_extension_documentation
|
||||
from build_instrumentation_method_map import generate_instrumentation_method_map
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@ -264,7 +268,21 @@ texinfo_documents = [
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
def run_apidoc(_):
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
cur_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
api_output = os.path.join(cur_dir, 'api')
|
||||
module = os.path.join(cur_dir, '..', '..', 'wlauto')
|
||||
main(['-f', '-o', api_output, module, '--force'])
|
||||
|
||||
def setup(app):
|
||||
module_dir = os.path.join('..', '..', 'wlauto')
|
||||
excluded_extensions = [os.path.join(module_dir, 'external'),
|
||||
os.path.join(module_dir, 'tests')]
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
app.connect('builder-inited', run_apidoc)
|
||||
generate_instrumentation_method_map('instrumentation_method_map.rst')
|
||||
generate_extension_documentation(module_dir, 'extensions', excluded_extensions)
|
||||
app.add_object_type('confval', 'confval',
|
||||
objname='configuration value',
|
||||
indextemplate='pair: %s; configuration value')
|
||||
|
@ -118,6 +118,7 @@ and detailed descriptions of how WA functions under the hood.
|
||||
additional_topics
|
||||
daq_device_setup
|
||||
revent
|
||||
apk_workloads
|
||||
contributing
|
||||
|
||||
API Reference
|
||||
|
@ -59,6 +59,11 @@ usually the best bet.
|
||||
Optionally (but recommended), you should also set ``ANDROID_HOME`` to point to
|
||||
the install location of the SDK (i.e. ``<path_to_android_sdk>/sdk``).
|
||||
|
||||
.. note:: You may need to install 32-bit compatibility libararies for the SDK
|
||||
to work properly. On Ubuntu you need to run::
|
||||
|
||||
sudo apt-get install lib32stdc++6 lib32z1
|
||||
|
||||
|
||||
Python
|
||||
------
|
||||
@ -87,7 +92,7 @@ similar distributions, this may be done with APT::
|
||||
If you do run into this issue after already installing some packages,
|
||||
you can resolve it by running ::
|
||||
|
||||
sudo chmod -R a+r /usr/local/lib/python2.7/dist-packagessudo
|
||||
sudo chmod -R a+r /usr/local/lib/python2.7/dist-packagessudo
|
||||
find /usr/local/lib/python2.7/dist-packages -type d -exec chmod a+x {} \;
|
||||
|
||||
(The paths above will work for Ubuntu; they may need to be adjusted
|
||||
@ -187,10 +192,28 @@ version $version".
|
||||
Some WA extensions have additional dependencies that need to be
|
||||
statisfied before they can be used. Not all of these can be provided with WA and
|
||||
so will need to be supplied by the user. They should be placed into
|
||||
``~/.workload_uatomation/dependencies/<extenion name>`` so that WA can find
|
||||
``~/.workload_automation/dependencies/<extenion name>`` so that WA can find
|
||||
them (you may need to create the directory if it doesn't already exist). You
|
||||
only need to provide the dependencies for workloads you want to use.
|
||||
|
||||
Binary Files
|
||||
------------
|
||||
|
||||
Some workloads require native binaries to work. Different binaries will be required
|
||||
for different ABIs. WA may not include the required binary for a workload due to
|
||||
licensing/distribution issues, or may not have a binary compiled for your device's
|
||||
ABI. In such cases, you will have to supply the missing binaries.
|
||||
|
||||
Executable binaries for a workload should be placed inside
|
||||
``~/.workload_automation/dependencies/<extension name>/bin/<ABI>`` directory.
|
||||
This directory may not already exist, in which case you would have to create it.
|
||||
|
||||
Binaries placed in that location will take precidence over any already inclueded with
|
||||
WA. For example, if you have your own ``drystone`` binary compiled for ``arm64``,
|
||||
and you want WA to pick it up, you can do the following on WA host machine ::
|
||||
|
||||
mkdir -p ~/.workload_automation/dependencies/dhrystone/bin/arm64/
|
||||
cp /path/to/your/dhrystone ~/.workload_automation/dependencies/dhrystone/bin/arm64/
|
||||
|
||||
APK Files
|
||||
---------
|
||||
@ -307,7 +330,7 @@ that location.
|
||||
|
||||
If you have installed Workload Automation via ``pip`` and wish to remove it, run this command to
|
||||
uninstall it::
|
||||
|
||||
|
||||
sudo -H pip uninstall wlauto
|
||||
|
||||
.. Note:: This will *not* remove any user configuration (e.g. the ~/.workload_automation directory)
|
||||
@ -317,5 +340,5 @@ uninstall it::
|
||||
====================
|
||||
|
||||
To upgrade Workload Automation to the latest version via ``pip``, run::
|
||||
|
||||
|
||||
sudo -H pip install --upgrade --no-deps wlauto
|
||||
|
@ -1,11 +1,12 @@
|
||||
.. _invocation:
|
||||
.. highlight:: none
|
||||
|
||||
========
|
||||
Commands
|
||||
========
|
||||
|
||||
Installing the wlauto package will add ``wa`` command to your system,
|
||||
which you can run from anywhere. This has a number of sub-commands, which can
|
||||
which you can run from anywhere. This has a number of sub-commands, which can
|
||||
be viewed by executing ::
|
||||
|
||||
wa -h
|
||||
@ -15,7 +16,7 @@ Individual sub-commands are discussed in detail below.
|
||||
run
|
||||
---
|
||||
|
||||
The most common sub-command you will use is ``run``. This will run specfied
|
||||
The most common sub-command you will use is ``run``. This will run specified
|
||||
workload(s) and process resulting output. This takes a single mandatory
|
||||
argument that specifies what you want WA to run. This could be either a
|
||||
workload name, or a path to an "agenda" file that allows to specify multiple
|
||||
@ -24,7 +25,7 @@ section for details). Executing ::
|
||||
|
||||
wa run -h
|
||||
|
||||
Will display help for this subcommand that will look somehtign like this::
|
||||
Will display help for this subcommand that will look something like this::
|
||||
|
||||
usage: run [-d DIR] [-f] AGENDA
|
||||
|
||||
@ -47,13 +48,13 @@ Will display help for this subcommand that will look somehtign like this::
|
||||
--debug Enable debug mode. Note: this implies --verbose.
|
||||
-d DIR, --output-directory DIR
|
||||
Specify a directory where the output will be
|
||||
generated. If the directoryalready exists, the script
|
||||
generated. If the directory already exists, the script
|
||||
will abort unless -f option (see below) is used,in
|
||||
which case the contents of the directory will be
|
||||
overwritten. If this optionis not specified, then
|
||||
overwritten. If this option is not specified, then
|
||||
wa_output will be used instead.
|
||||
-f, --force Overwrite output directory if it exists. By default,
|
||||
the script will abort in thissituation to prevent
|
||||
the script will abort in this situation to prevent
|
||||
accidental data loss.
|
||||
-i ID, --id ID Specify a workload spec ID from an agenda to run. If
|
||||
this is specified, only that particular spec will be
|
||||
@ -81,10 +82,74 @@ agenda file used to run the workloads along with any other device-specific
|
||||
configuration files used during execution.
|
||||
|
||||
|
||||
create
|
||||
------
|
||||
|
||||
This can be used to create various WA-related objects, currently workloads, packages and agendas.
|
||||
The full set of options for this command are::
|
||||
|
||||
usage: wa create [-h] [-c CONFIG] [-v] [--debug] [--version]
|
||||
{workload,package,agenda} ...
|
||||
|
||||
positional arguments:
|
||||
{workload,package,agenda}
|
||||
workload Create a new workload. By default, a basic workload
|
||||
template will be used but you can use options to
|
||||
specify a different template.
|
||||
package Create a new empty Python package for WA extensions.
|
||||
On installation, this package will "advertise" itself
|
||||
to WA so that Extensions with in it will be loaded by
|
||||
WA when it runs.
|
||||
agenda Create an agenda whit the specified extensions
|
||||
enabled. And parameters set to their default values.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
specify an additional config.py
|
||||
-v, --verbose The scripts will produce verbose output.
|
||||
--debug Enable debug mode. Note: this implies --verbose.
|
||||
--version show program's version number and exit
|
||||
|
||||
Use "wa create <object> -h" to see all the object-specific arguments. For example::
|
||||
|
||||
wa create agenda -h
|
||||
|
||||
will display the relevant options that can be used to create an agenda.
|
||||
|
||||
get-assets
|
||||
----------
|
||||
|
||||
This command can download external extension dependencies used by Workload Automation.
|
||||
It can be used to download assets for all available extensions or those specificity listed.
|
||||
The full set of options for this command are::
|
||||
|
||||
usage: wa get-assets [-h] [-c CONFIG] [-v] [--debug] [--version] [-f]
|
||||
[--url URL] (-a | -e EXT [EXT ...])
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
specify an additional config.py
|
||||
-v, --verbose The scripts will produce verbose output.
|
||||
--debug Enable debug mode. Note: this implies --verbose.
|
||||
--version show program's version number and exit
|
||||
-f, --force Always fetch the assets, even if matching versions
|
||||
exist in local cache.
|
||||
--url URL The location from which to download the files. If not
|
||||
provided, config setting ``remote_assets_url`` will be
|
||||
used if available, else uses the default
|
||||
REMOTE_ASSETS_URL parameter in the script.
|
||||
-a, --all Download assets for all extensions found in the index.
|
||||
Cannot be used with -e.
|
||||
-e EXT [EXT ...] One or more extensions whose assets to download.
|
||||
Cannot be used with --all.
|
||||
|
||||
|
||||
list
|
||||
----
|
||||
|
||||
This lists all extensions of a particular type. For example ::
|
||||
This lists all extensions of a particular type. For example::
|
||||
|
||||
wa list workloads
|
||||
|
||||
@ -97,11 +162,11 @@ show
|
||||
|
||||
This will show detailed information about an extension, including more in-depth
|
||||
description and any parameters/configuration that are available. For example
|
||||
executing ::
|
||||
executing::
|
||||
|
||||
wa show andebench
|
||||
|
||||
will produce something like ::
|
||||
will produce something like::
|
||||
|
||||
|
||||
andebench
|
||||
@ -131,5 +196,64 @@ will produce something like ::
|
||||
- Results displayed in Iterations per second
|
||||
- Detailed log file for comprehensive engineering analysis
|
||||
|
||||
.. _record-command:
|
||||
|
||||
record
|
||||
------
|
||||
|
||||
This command simplifies the process of recording an revent file. It
|
||||
will automatically deploy revent and even has the option of automatically
|
||||
opening apps. WA uses two parts to the names of revent recordings in the
|
||||
format, {device_name}.{suffix}.revent. - device_name can either be specified
|
||||
manually with the ``-d`` argument or it can be automatically determined. On
|
||||
Android device it will be obtained from ``build.prop``, on Linux devices it is
|
||||
obtained from ``/proc/device-tree/model``. - suffix is used by WA to determine
|
||||
which part of the app execution the recording is for, currently these are
|
||||
either ``setup`` or ``run``. This should be specified with the ``-s``
|
||||
argument. The full set of options for this command are::
|
||||
|
||||
usage: wa record [-h] [-c CONFIG] [-v] [--debug] [--version] [-d DEVICE]
|
||||
[-s SUFFIX] [-o OUTPUT] [-p PACKAGE] [-g] [-C]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
specify an additional config.py
|
||||
-v, --verbose The scripts will produce verbose output.
|
||||
--debug Enable debug mode. Note: this implies --verbose.
|
||||
--version show program's version number and exit
|
||||
-d DEVICE, --device DEVICE
|
||||
The name of the device
|
||||
-s SUFFIX, --suffix SUFFIX
|
||||
The suffix of the revent file, e.g. ``setup``
|
||||
-o OUTPUT, --output OUTPUT
|
||||
Directory to save the recording in
|
||||
-p PACKAGE, --package PACKAGE
|
||||
Package to launch before recording
|
||||
-g, --gamepad Record from a gamepad rather than all devices.
|
||||
-C, --clear Clear app cache before launching it
|
||||
|
||||
.. _replay-command:
|
||||
|
||||
replay
|
||||
------
|
||||
|
||||
Along side ``record`` wa also has a command to playback recorded revent files.
|
||||
It behaves very similar to the ``record`` command taking many of the same options::
|
||||
|
||||
usage: wa replay [-h] [-c CONFIG] [-v] [--debug] [--version] [-p PACKAGE] [-C]
|
||||
revent
|
||||
|
||||
positional arguments:
|
||||
revent The name of the file to replay
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
specify an additional config.py
|
||||
-v, --verbose The scripts will produce verbose output.
|
||||
--debug Enable debug mode. Note: this implies --verbose.
|
||||
--version show program's version number and exit
|
||||
-p PACKAGE, --package PACKAGE
|
||||
Package to launch before recording
|
||||
-C, --clear Clear app cache before launching it
|
||||
|
@ -1,7 +1,10 @@
|
||||
.. _revent_files_creation:
|
||||
|
||||
revent
|
||||
======
|
||||
++++++
|
||||
|
||||
Overview and Usage
|
||||
==================
|
||||
|
||||
revent utility can be used to record and later play back a sequence of user
|
||||
input events, such as key presses and touch screen taps. This is an alternative
|
||||
@ -17,36 +20,47 @@ to Android UI Automator for providing automation for workloads. ::
|
||||
info:shows info about each event char device
|
||||
any additional parameters make it verbose
|
||||
|
||||
|
||||
.. note:: There are now also WA commands that perform the below steps.
|
||||
Please see ``wa show record/replay`` and ``wa record/replay --help``
|
||||
for details.
|
||||
|
||||
Recording
|
||||
---------
|
||||
|
||||
To record, transfer the revent binary to the device, then invoke ``revent
|
||||
record``, giving it the time (in seconds) you want to record for, and the
|
||||
file you want to record to (WA expects these files to have .revent
|
||||
extension)::
|
||||
WA features a ``record`` command that will automatically deploy and start
|
||||
revent on the target device::
|
||||
|
||||
host$ adb push revent /data/local/revent
|
||||
host$ adb shell
|
||||
device# cd /data/local
|
||||
device# ./revent record 1000 my_recording.revent
|
||||
wa record
|
||||
INFO Connecting to device...
|
||||
INFO Press Enter when you are ready to record...
|
||||
[Pressed Enter]
|
||||
INFO Press Enter when you have finished recording...
|
||||
[Pressed Enter]
|
||||
INFO Pulling files from device
|
||||
|
||||
Once started, you will need to get the target device ready to record (e.g.
|
||||
unlock screen, navigate menus and launch an app) then press ``ENTER``.
|
||||
The recording has now started and button presses, taps, etc you perform on
|
||||
the device will go into the .revent file. To stop the recording simply press
|
||||
``ENTER`` again.
|
||||
|
||||
Once you have finished recording the revent file will be pulled from the device
|
||||
to the current directory. It will be named ``{device_model}.revent``. When
|
||||
recording revent files for a ``GameWorkload`` you can use the ``-s`` option to
|
||||
add ``run`` or ``setup`` suffixes.
|
||||
|
||||
From version 2.6 of WA onwards, a "gamepad" recording mode is also supported.
|
||||
This mode requires a gamepad to be connected to the device when recoridng, but
|
||||
the recordings produced in this mode should be portable across devices.
|
||||
|
||||
For more information run please read :ref:`record-command`
|
||||
|
||||
The recording has now started and button presses, taps, etc you perform on the
|
||||
device will go into the .revent file. The recording will stop after the
|
||||
specified time period, and you can also stop it by hitting return in the adb
|
||||
shell.
|
||||
|
||||
Replaying
|
||||
---------
|
||||
|
||||
To replay a recorded file, run ``revent replay`` on the device, giving it the
|
||||
file you want to replay::
|
||||
To replay a recorded file, run ``wa replay``, giving it the file you want to
|
||||
replay::
|
||||
|
||||
device# ./revent replay my_recording.revent
|
||||
wa replay my_recording.revent
|
||||
|
||||
For more information run please read :ref:`replay-command`
|
||||
|
||||
|
||||
Using revent With Workloads
|
||||
@ -100,3 +114,359 @@ where as UI Automator only works for Android UI elements (such as text boxes or
|
||||
radio buttons), which makes the latter useless for things like games. Recording
|
||||
revent sequence is also faster than writing automation code (on the other hand,
|
||||
one would need maintain a different revent log for each screen resolution).
|
||||
|
||||
|
||||
Using state detection with revent
|
||||
=================================
|
||||
|
||||
State detection can be used to verify that a workload is executing as expected.
|
||||
This utility, if enabled, and if state definitions are available for the
|
||||
particular workload, takes a screenshot after the setup and the run revent
|
||||
sequence, matches the screenshot to a state and compares with the expected
|
||||
state. A WorkloadError is raised if an unexpected state is encountered.
|
||||
|
||||
To enable state detection, make sure a valid state definition file and
|
||||
templates exist for your workload and set the check_states parameter to True.
|
||||
|
||||
State definition directory
|
||||
--------------------------
|
||||
|
||||
State and phase definitions should be placed in a directory of the following
|
||||
structure inside the dependencies directory of each workload (along with
|
||||
revent files etc):
|
||||
|
||||
::
|
||||
|
||||
dependencies/
|
||||
<workload_name>/
|
||||
state_definitions/
|
||||
definition.yaml
|
||||
templates/
|
||||
<oneTemplate>.png
|
||||
<anotherTemplate>.png
|
||||
...
|
||||
|
||||
definition.yaml file
|
||||
--------------------
|
||||
|
||||
This defines each state of the workload and lists which templates are expected
|
||||
to be found and how many are required to be detected for a conclusive match. It
|
||||
also defines the expected state in each workload phase where a state detection
|
||||
is run (currently those are setup_complete and run_complete).
|
||||
|
||||
Templates are picture elements to be matched in a screenshot. Each template
|
||||
mentioned in the definition file should be placed as a file with the same name
|
||||
and a .png extension inside the templates folder. Creating template png files
|
||||
is as simple as taking a screenshot of the workload in a given state, cropping
|
||||
out the relevant templates (eg. a button, label or other unique element that is
|
||||
present in that state) and storing them in PNG format.
|
||||
|
||||
Please see the definition file for Angry Birds below as an example to
|
||||
understand the format. Note that more than just two states (for the afterSetup
|
||||
and afterRun phase) can be defined and this helps track the cause of errors in
|
||||
case an unexpected state is encountered.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
workload_name: angrybirds
|
||||
|
||||
workload_states:
|
||||
- state_name: titleScreen
|
||||
templates:
|
||||
- play_button
|
||||
- logo
|
||||
matches: 2
|
||||
- state_name: worldSelection
|
||||
templates:
|
||||
- first_world_thumb
|
||||
- second_world_thumb
|
||||
- third_world_thumb
|
||||
- fourth_world_thumb
|
||||
matches: 3
|
||||
- state_name: level_selection
|
||||
templates:
|
||||
- locked_level
|
||||
- first_level
|
||||
matches: 2
|
||||
- state_name: gameplay
|
||||
templates:
|
||||
- pause_button
|
||||
- score_label_text
|
||||
matches: 2
|
||||
- state_name: pause_screen
|
||||
templates:
|
||||
- replay_button
|
||||
- menu_button
|
||||
- resume_button
|
||||
- help_button
|
||||
matches: 4
|
||||
- state_name: level_cleared_screen
|
||||
templates:
|
||||
- level_cleared_text
|
||||
- menu_button
|
||||
- replay_button
|
||||
- fast_forward_button
|
||||
matches: 4
|
||||
|
||||
workload_phases:
|
||||
- phase_name: setup_complete
|
||||
expected_state: gameplay
|
||||
- phase_name: run_complete
|
||||
expected_state: level_cleared_screen
|
||||
|
||||
|
||||
File format of revent recordings
|
||||
================================
|
||||
|
||||
You do not need to understand recording format in order to use revent. This
|
||||
section is intended for those looking to extend revent in some way, or to
|
||||
utilize revent recordings for other purposes.
|
||||
|
||||
Format Overview
|
||||
---------------
|
||||
|
||||
Recordings are stored in a binary format. A recording consists of three
|
||||
sections::
|
||||
|
||||
+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Header |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| Device Description |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| |
|
||||
| Event Stream |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
The header contains metadata describing the recording. The device description
|
||||
contains information about input devices involved in this recording. Finally,
|
||||
the event stream contains the recorded input events.
|
||||
|
||||
All fields are either fixed size or prefixed with their length or the number of
|
||||
(fixed-sized) elements.
|
||||
|
||||
.. note:: All values below are little endian
|
||||
|
||||
|
||||
Recording Header
|
||||
----------------
|
||||
|
||||
An revent recoding header has the following structure
|
||||
|
||||
* It starts with the "magic" string ``REVENT`` to indicate that this is an
|
||||
revent recording.
|
||||
* The magic is followed by a 16 bit version number. This indicates the format
|
||||
version of the recording that follows. Current version is ``2``.
|
||||
* The next 16 bits indicate the type of the recording. This dictates the
|
||||
structure of the Device Description section. Valid values are:
|
||||
|
||||
``0``
|
||||
This is a general input event recording. The device description
|
||||
contains a list of paths from which the events where recorded.
|
||||
``1``
|
||||
This a gamepad recording. The device description contains the
|
||||
description of the gamepad used to create the recording.
|
||||
|
||||
* The header is zero-padded to 128 bits.
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 'R' | 'E' | 'V' | 'E' |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| 'N' | 'T' | Version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Mode | PADDING |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| PADDING |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
Device Description
|
||||
------------------
|
||||
|
||||
This section describes the input devices used in the recording. Its structure is
|
||||
determined by the value of ``Mode`` field in the header.
|
||||
|
||||
general recording
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note:: This is the only format supported prior to version ``2``.
|
||||
|
||||
The recording has been made from all available input devices. This section
|
||||
contains the list of ``/dev/input`` paths for the devices, prefixed with total
|
||||
number of the devices recorded.
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of devices |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| Device paths +-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
Similarly, each device path is a length-prefixed string. Unlike C strings, the
|
||||
path is *not* NULL-terminated.
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of device path |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| Device path |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
gamepad recording
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The recording has been made from a specific gamepad. All events in the stream
|
||||
will be for that device only. The section describes the device properties that
|
||||
will be used to create a virtual input device using ``/dev/uinput``. Please
|
||||
see ``linux/input.h`` header in the Linux kernel source for more information
|
||||
about the fields in this section.
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| bustype | vendor |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| product | version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| name_length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| name |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| ev_bits |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| |
|
||||
| key_bits (96 bytes) |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| |
|
||||
| rel_bits (96 bytes) |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| |
|
||||
| abs_bits (96 bytes) |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| num_absinfo |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| absinfo entries |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
Each ``absinfo`` entry consists of six 32 bit values. The number of entries is
|
||||
determined by the ``abs_bits`` field.
|
||||
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| value |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| minimum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| maximum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| fuzz |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| flat |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| resolution |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
Event structure
|
||||
---------------
|
||||
|
||||
The majority of an revent recording will be made up of the input events that were
|
||||
recorded. The event stream is prefixed with the number of events in the stream.
|
||||
|
||||
Each event entry structured as follows:
|
||||
|
||||
* An unsigned integer representing which device from the list of device paths
|
||||
this event is for (zero indexed). E.g. Device ID = 3 would be the 4th
|
||||
device in the list of device paths.
|
||||
* A signed integer representing the number of seconds since "epoch" when the
|
||||
event was recorded.
|
||||
* A signed integer representing the microseconds part of the timestamp.
|
||||
* An unsigned integer representing the event type
|
||||
* An unsigned integer representing the event code
|
||||
* An unsigned integer representing the event value
|
||||
|
||||
For more information about the event type, code and value please read:
|
||||
https://www.kernel.org/doc/Documentation/input/event-codes.txt
|
||||
|
||||
::
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Device ID | Timestamp Seconds |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Timestamp Seconds (cont.) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Timestamp Seconds (cont.) | stamp Micoseconds |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Timestamp Micoseconds (cont.) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Timestamp Micoseconds (cont.) | Event Type |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Event Code | Event Value |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Event Value (cont.) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
Parser
|
||||
------
|
||||
|
||||
WA has a parser for revent recordings. This can be used to work with revent
|
||||
recordings in scripts. Here is an example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from wlauto.utils.revent import ReventRecording
|
||||
|
||||
with ReventRecording('/path/to/recording.revent') as recording:
|
||||
print "Recording: {}".format(recording.filepath)
|
||||
print "There are {} input events".format(recording.num_events)
|
||||
print "Over a total of {} seconds".format(recording.duration)
|
||||
|
@ -161,8 +161,8 @@ In order for the binary to be obtained in this way, it must be stored in one of
|
||||
the locations scanned by the resource resolver in a directry structure
|
||||
``<root>/bin/<abi>/<binary>`` (where ``root`` is the base resource location to
|
||||
be searched, e.g. ``~/.workload_automation/depencencies/<extension name>``, and
|
||||
``<abi>`` is the ABI for which the exectuable has been compiled, as returned by
|
||||
``self.device.abi``).
|
||||
``<abi>`` is the ABI for which the exectuable has been compiled, as returned by
|
||||
``self.device.abi``).
|
||||
|
||||
Once the path to the host-side binary has been obtained, it may be deployed using
|
||||
one of two methods of a ``Device`` instace -- ``install`` or ``install_if_needed``.
|
||||
@ -182,8 +182,8 @@ WA and will not try to re-install.
|
||||
|
||||
Both of the above methods will return the path to the installed binary on the
|
||||
device. The executable should be invoked *only* via that path; do **not** assume
|
||||
that it will be in ``PATH`` on the target (or that the executable with the same
|
||||
name in ``PATH`` is the version deployed by WA.
|
||||
that it will be in ``PATH`` on the target (or that the executable with the same
|
||||
name in ``PATH`` is the version deployed by WA.
|
||||
|
||||
.. code:: python
|
||||
|
||||
@ -964,7 +964,7 @@ that accompanies them, in addition to what has been outlined here, should
|
||||
provide enough guidance.
|
||||
|
||||
:commands: This allows extending WA with additional sub-commands (to supplement
|
||||
exiting ones outlined in the :ref:`invocation` section).
|
||||
exiting ones outlined in the :ref:`invocation <invocation>` section).
|
||||
:modules: Modules are "extensions for extensions". They can be loaded by other
|
||||
extensions to expand their functionality (for example, a flashing
|
||||
module maybe loaded by a device in order to support flashing).
|
||||
|
3
setup.py
3
setup.py
@ -66,7 +66,7 @@ params = dict(
|
||||
packages=packages,
|
||||
package_data=data_files,
|
||||
scripts=scripts,
|
||||
url='N/A',
|
||||
url='http://github.com/arm-sowftware/workload-automation',
|
||||
license='Apache v2',
|
||||
maintainer='ARM Architecture & Technology Device Lab',
|
||||
maintainer_email='workload-automation@arm.com',
|
||||
@ -80,6 +80,7 @@ params = dict(
|
||||
],
|
||||
extras_require={
|
||||
'other': ['jinja2', 'pandas>=0.13.1'],
|
||||
'statedetect': ['numpy', 'imutils', 'opencv-python'],
|
||||
'test': ['nose'],
|
||||
'mongodb': ['pymongo'],
|
||||
'notify': ['notify2'],
|
||||
|
@ -29,7 +29,7 @@ from wlauto.common.linux.device import LinuxDevice # NOQA
|
||||
from wlauto.common.android.device import AndroidDevice, BigLittleDevice # NOQA
|
||||
from wlauto.common.android.resources import ApkFile, JarFile
|
||||
from wlauto.common.android.workload import (UiAutomatorWorkload, ApkWorkload, AndroidBenchmark, # NOQA
|
||||
AndroidUiAutoBenchmark, GameWorkload) # NOQA
|
||||
AndroidUiAutoBenchmark, AndroidUxPerfWorkload, GameWorkload) # NOQA
|
||||
|
||||
from wlauto.core.version import get_wa_version
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# This agenda specifies configuration that may be used for regression runs
|
||||
# on big.LITTLE systems. This agenda will with a TC2 device configured as
|
||||
# described in the documentation.
|
||||
# on big.LITTLE systems. This agenda will work with a TC2 device configured
|
||||
# as described in the documentation.
|
||||
config:
|
||||
device: tc2
|
||||
run_name: big.LITTLE_regression
|
||||
@ -69,7 +69,7 @@ workloads:
|
||||
- id: b10
|
||||
name: smartbench
|
||||
- id: b11
|
||||
name: sqlite
|
||||
name: sqlitebm
|
||||
- id: b12
|
||||
name: vellamo
|
||||
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -22,7 +22,6 @@ import textwrap
|
||||
import argparse
|
||||
import shutil
|
||||
import getpass
|
||||
import subprocess
|
||||
from collections import OrderedDict
|
||||
|
||||
import yaml
|
||||
@ -30,8 +29,9 @@ import yaml
|
||||
from wlauto import ExtensionLoader, Command, settings
|
||||
from wlauto.exceptions import CommandError, ConfigError
|
||||
from wlauto.utils.cli import init_argument_parser
|
||||
from wlauto.utils.misc import (capitalize, check_output,
|
||||
ensure_file_directory_exists as _f, ensure_directory_exists as _d)
|
||||
from wlauto.utils.misc import (capitalize,
|
||||
ensure_file_directory_exists as _f,
|
||||
ensure_directory_exists as _d)
|
||||
from wlauto.utils.types import identifier
|
||||
from wlauto.utils.doc import format_body
|
||||
|
||||
@ -41,20 +41,6 @@ __all__ = ['create_workload']
|
||||
|
||||
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
|
||||
UIAUTO_BUILD_SCRIPT = """#!/bin/bash
|
||||
|
||||
class_dir=bin/classes/com/arm/wlauto/uiauto
|
||||
base_class=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', 'BaseUiAutomation.class')"`
|
||||
mkdir -p $$class_dir
|
||||
cp $$base_class $$class_dir
|
||||
|
||||
ant build
|
||||
|
||||
if [[ -f bin/${package_name}.jar ]]; then
|
||||
cp bin/${package_name}.jar ..
|
||||
fi
|
||||
"""
|
||||
|
||||
|
||||
class CreateSubcommand(object):
|
||||
|
||||
@ -321,7 +307,7 @@ def create_basic_workload(path, name, class_name):
|
||||
|
||||
|
||||
def create_uiautomator_workload(path, name, class_name):
|
||||
uiauto_path = _d(os.path.join(path, 'uiauto'))
|
||||
uiauto_path = os.path.join(path, 'uiauto')
|
||||
create_uiauto_project(uiauto_path, name)
|
||||
source_file = os.path.join(path, '__init__.py')
|
||||
with open(source_file, 'w') as wfh:
|
||||
@ -335,37 +321,34 @@ def create_android_benchmark(path, name, class_name):
|
||||
|
||||
|
||||
def create_android_uiauto_benchmark(path, name, class_name):
|
||||
uiauto_path = _d(os.path.join(path, 'uiauto'))
|
||||
uiauto_path = os.path.join(path, 'uiauto')
|
||||
create_uiauto_project(uiauto_path, name)
|
||||
source_file = os.path.join(path, '__init__.py')
|
||||
with open(source_file, 'w') as wfh:
|
||||
wfh.write(render_template('android_uiauto_benchmark', {'name': name, 'class_name': class_name}))
|
||||
|
||||
|
||||
def create_uiauto_project(path, name, target='1'):
|
||||
sdk_path = get_sdk_path()
|
||||
android_path = os.path.join(sdk_path, 'tools', 'android')
|
||||
def create_uiauto_project(path, name):
|
||||
package_name = 'com.arm.wlauto.uiauto.' + name.lower()
|
||||
|
||||
# ${ANDROID_HOME}/tools/android create uitest-project -n com.arm.wlauto.uiauto.linpack -t 1 -p ../test2
|
||||
command = '{} create uitest-project --name {} --target {} --path {}'.format(android_path,
|
||||
package_name,
|
||||
target,
|
||||
path)
|
||||
try:
|
||||
check_output(command, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if 'is is not valid' in e.output:
|
||||
message = 'No Android SDK target found; have you run "{} update sdk" and download a platform?'
|
||||
raise CommandError(message.format(android_path))
|
||||
shutil.copytree(os.path.join(TEMPLATES_DIR, 'uiauto_template'), path)
|
||||
|
||||
manifest_path = os.path.join(path, 'app', 'src', 'main')
|
||||
mainifest = os.path.join(_d(manifest_path), 'AndroidManifest.xml')
|
||||
with open(mainifest, 'w') as wfh:
|
||||
wfh.write(render_template('uiauto_AndroidManifest.xml', {'package_name': package_name}))
|
||||
|
||||
build_gradle_path = os.path.join(path, 'app')
|
||||
build_gradle = os.path.join(_d(build_gradle_path), 'build.gradle')
|
||||
with open(build_gradle, 'w') as wfh:
|
||||
wfh.write(render_template('uiauto_build.gradle', {'package_name': package_name}))
|
||||
|
||||
build_script = os.path.join(path, 'build.sh')
|
||||
with open(build_script, 'w') as wfh:
|
||||
template = string.Template(UIAUTO_BUILD_SCRIPT)
|
||||
wfh.write(template.substitute({'package_name': package_name}))
|
||||
wfh.write(render_template('uiauto_build_script', {'package_name': package_name}))
|
||||
os.chmod(build_script, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
|
||||
source_file = _f(os.path.join(path, 'src',
|
||||
source_file = _f(os.path.join(path, 'app', 'src', 'main', 'java',
|
||||
os.sep.join(package_name.split('.')[:-1]),
|
||||
'UiAutomation.java'))
|
||||
with open(source_file, 'w') as wfh:
|
||||
|
122
wlauto/commands/get_assets.py
Normal file
122
wlauto/commands/get_assets.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright 2014-2015 ARM Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from requests import ConnectionError, RequestException
|
||||
|
||||
from wlauto import File, ExtensionLoader, Command, settings
|
||||
from wlauto.core.extension import Extension
|
||||
|
||||
|
||||
REMOTE_ASSETS_URL = 'https://github.com/ARM-software/workload-automation-assets/raw/master/dependencies'
|
||||
|
||||
|
||||
class GetAssetsCommand(Command):
|
||||
name = 'get-assets'
|
||||
description = '''
|
||||
This command downloads external extension dependencies used by Workload Automation.
|
||||
Works by first downloading a directory index of the assets, then iterating through
|
||||
it to get assets for the specified extensions.
|
||||
'''
|
||||
|
||||
# Uses config setting if available otherwise defaults to ARM-software repo
|
||||
# Can be overriden with the --url argument
|
||||
assets_url = settings.remote_assets_url or REMOTE_ASSETS_URL
|
||||
|
||||
def initialize(self, context):
|
||||
self.parser.add_argument('-f', '--force', action='store_true',
|
||||
help='Always fetch the assets, even if matching versions exist in local cache.')
|
||||
self.parser.add_argument('--url', metavar='URL', type=not_empty, default=self.assets_url,
|
||||
help='''The location from which to download the files. If not provided,
|
||||
config setting ``remote_assets_url`` will be used if available, else
|
||||
uses the default REMOTE_ASSETS_URL parameter in the script.''')
|
||||
group = self.parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('-a', '--all', action='store_true',
|
||||
help='Download assets for all extensions found in the index. Cannot be used with -e.')
|
||||
group.add_argument('-e', dest='exts', metavar='EXT', nargs='+', type=not_empty,
|
||||
help='One or more extensions whose assets to download. Cannot be used with --all.')
|
||||
|
||||
def execute(self, args):
|
||||
self.logger.debug('Program arguments: {}'.format(vars(args)))
|
||||
if args.force:
|
||||
self.logger.info('Force-download of assets requested')
|
||||
if not args.url:
|
||||
self.logger.debug('URL not provided, falling back to default setting in config')
|
||||
self.logger.info('Downloading external assets from {}'.format(args.url))
|
||||
|
||||
# Get file index of assets
|
||||
ext_loader = ExtensionLoader(packages=settings.extension_packages, paths=settings.extension_paths)
|
||||
getter = ext_loader.get_resource_getter('http_assets', None, url=args.url, always_fetch=args.force)
|
||||
try:
|
||||
getter.index = getter.fetch_index()
|
||||
except (ConnectionError, RequestException) as e:
|
||||
self.exit_with_error(str(e))
|
||||
all_assets = dict()
|
||||
for k, v in getter.index.iteritems():
|
||||
all_assets[str(k)] = [str(asset['path']) for asset in v]
|
||||
|
||||
# Here we get a list of all extensions present in the current WA installation,
|
||||
# and cross-check that against the list of extensions whose assets are requested.
|
||||
# The aim is to avoid downloading assets for extensions that do not exist, since
|
||||
# WA extensions and asset index can be updated independently and go out of sync.
|
||||
all_extensions = [ext.name for ext in ext_loader.list_extensions()]
|
||||
assets_to_get = set(all_assets).intersection(all_extensions)
|
||||
if args.exts:
|
||||
assets_to_get = assets_to_get.intersection(args.exts)
|
||||
# Check list is not empty
|
||||
if not assets_to_get:
|
||||
if args.all:
|
||||
self.exit_with_error('Could not find extensions: {}'.format(', '.join(all_assets.keys())))
|
||||
else: # args.exts
|
||||
self.exit_with_error('Asset index has no entries for: {}'.format(', '.join(args.exts)))
|
||||
|
||||
# Check out of sync extensions i.e. do not exist in both WA and assets index
|
||||
missing = set(all_assets).difference(all_extensions) | set(args.exts or []).difference(all_assets)
|
||||
if missing:
|
||||
self.logger.warning('Not getting assets for missing extensions: {}'.format(', '.join(missing)))
|
||||
|
||||
# Ideally the extension loader would be used to instantiate, but it does full
|
||||
# validation of the extension, like checking connected devices or supported
|
||||
# platform(s). This info might be unavailable and is not required to download
|
||||
# assets, since they are classified by extension name alone. So instead we use
|
||||
# a simple subclass of ``Extension`` providing a valid ``name`` attribute.
|
||||
for ext_name in assets_to_get:
|
||||
owner = _instantiate(NamedExtension, ext_name)
|
||||
self.logger.info('Getting assets for: {}'.format(ext_name))
|
||||
for asset in all_assets[ext_name]:
|
||||
getter.get(File(owner, asset)) # Download the files
|
||||
|
||||
def exit_with_error(self, message, code=1):
|
||||
self.logger.error(message)
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
class NamedExtension(Extension):
|
||||
def __init__(self, name, **kwargs):
|
||||
super(NamedExtension, self).__init__(**kwargs)
|
||||
self.name = name
|
||||
|
||||
|
||||
def not_empty(val):
|
||||
if val:
|
||||
return val
|
||||
else:
|
||||
raise argparse.ArgumentTypeError('Extension name cannot be blank')
|
||||
|
||||
|
||||
def _instantiate(cls, *args, **kwargs):
|
||||
return cls(*args, **kwargs)
|
@ -15,6 +15,8 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
from math import ceil
|
||||
|
||||
from wlauto import ExtensionLoader, Command, settings
|
||||
from wlauto.common.resources import Executable
|
||||
@ -22,40 +24,10 @@ from wlauto.core.resource import NO_ONE
|
||||
from wlauto.core.resolver import ResourceResolver
|
||||
from wlauto.core.configuration import RunConfiguration
|
||||
from wlauto.core.agenda import Agenda
|
||||
from wlauto.utils.revent import ReventRecording, GAMEPAD_MODE
|
||||
|
||||
|
||||
class RecordCommand(Command):
|
||||
|
||||
name = 'record'
|
||||
description = '''Performs a revent recording
|
||||
|
||||
This command helps making revent recordings. It will automatically
|
||||
deploy revent and even has the option of automatically opening apps.
|
||||
|
||||
Revent allows you to record raw inputs such as screen swipes or button presses.
|
||||
This can be useful for recording inputs for workloads such as games that don't
|
||||
have XML UI layouts that can be used with UIAutomator. As a drawback from this,
|
||||
revent recordings are specific to the device type they were recorded on.
|
||||
|
||||
WA uses two parts to the names of revent recordings in the format,
|
||||
{device_name}.{suffix}.revent.
|
||||
|
||||
- device_name can either be specified manually with the ``-d`` argument or
|
||||
it can be automatically determined. On Android device it will be obtained
|
||||
from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``.
|
||||
- suffix is used by WA to determine which part of the app execution the
|
||||
recording is for, currently these are either ``setup`` or ``run``. This
|
||||
should be specified with the ``-s`` argument.
|
||||
'''
|
||||
|
||||
def initialize(self, context):
|
||||
self.context = context
|
||||
self.parser.add_argument('-d', '--device', help='The name of the device')
|
||||
self.parser.add_argument('-s', '--suffix', help='The suffix of the revent file, e.g. ``setup``')
|
||||
self.parser.add_argument('-o', '--output', help='Directory to save the recording in')
|
||||
self.parser.add_argument('-p', '--package', help='Package to launch before recording')
|
||||
self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it',
|
||||
action="store_true")
|
||||
class ReventCommand(Command):
|
||||
|
||||
# Validate command options
|
||||
def validate_args(self, args):
|
||||
@ -89,21 +61,83 @@ class RecordCommand(Command):
|
||||
self.device.initialize(context)
|
||||
|
||||
host_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
||||
self.target_binary = self.device.install_if_needed(host_binary)
|
||||
self.target_binary = self.device.install_executable(host_binary)
|
||||
|
||||
self.run(args)
|
||||
|
||||
def run(self, args):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RecordCommand(ReventCommand):
|
||||
|
||||
name = 'record'
|
||||
description = '''Performs a revent recording
|
||||
|
||||
This command helps create revent recordings. It will automatically
|
||||
deploy revent and even has the option of automatically opening apps.
|
||||
|
||||
Revent allows you to record raw inputs such as screen swipes or button presses.
|
||||
This can be useful for recording inputs for workloads such as games that don't
|
||||
have XML UI layouts that can be used with UIAutomator. As a drawback from this,
|
||||
revent recordings are specific to the device type they were recorded on.
|
||||
|
||||
WA uses two parts to form the names of revent recordings in the format,
|
||||
{device_name}.{suffix}.revent
|
||||
|
||||
- device_name can either be specified manually with the ``-d`` argument or
|
||||
else the name of the device will be automatically determined. On an Android device it is obtained
|
||||
from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``.
|
||||
- suffix is used by WA to determine which part of the app execution the
|
||||
recording is for, currently either ``setup``, ``run`` or ``teardown``. This
|
||||
should be specified with the ``-s`` argument.
|
||||
|
||||
|
||||
**gamepad recording**
|
||||
|
||||
revent supports an alternative recording mode, where it will record events
|
||||
from a single gamepad device. In this mode, revent will store the
|
||||
description of this device as a part of the recording. When replaying such
|
||||
a recording, revent will first create a virtual gamepad using the
|
||||
description, and will replay the events into it, so a physical controller
|
||||
does not need to be connected on replay. Unlike standard revent recordings,
|
||||
recordings generated in this mode should be (to an extent) portable across
|
||||
different devices.
|
||||
|
||||
note:
|
||||
|
||||
- The device on which a recording is being made in gamepad mode, must have
|
||||
exactly one gamepad connected to it.
|
||||
- The device on which a gamepad recording is being replayed must have
|
||||
/dev/uinput enabled in the kernel (this interface is necessary to create
|
||||
virtual gamepad).
|
||||
|
||||
'''
|
||||
|
||||
def initialize(self, context):
|
||||
self.context = context
|
||||
self.parser.add_argument('-d', '--device', help='The name of the device')
|
||||
self.parser.add_argument('-s', '--suffix', help='The suffix of the revent file, e.g. ``setup``')
|
||||
self.parser.add_argument('-o', '--output', help='Directory to save the recording in')
|
||||
self.parser.add_argument('-p', '--package', help='Package to launch before recording')
|
||||
self.parser.add_argument('-g', '--gamepad', help='Record from a gamepad rather than all devices.',
|
||||
action="store_true")
|
||||
self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it',
|
||||
action="store_true")
|
||||
self.parser.add_argument('-S', '--capture-screen', help='Record a screen capture after recording',
|
||||
action="store_true")
|
||||
|
||||
def run(self, args):
|
||||
if args.device:
|
||||
self.device_name = args.device
|
||||
device_name = args.device
|
||||
else:
|
||||
self.device_name = self.device.get_device_model()
|
||||
device_name = self.device.get_device_model()
|
||||
|
||||
if args.suffix:
|
||||
args.suffix += "."
|
||||
|
||||
revent_file = self.device.path.join(self.device.working_directory,
|
||||
'{}.{}revent'.format(self.device_name, args.suffix or ""))
|
||||
'{}.{}revent'.format(device_name, args.suffix or ""))
|
||||
|
||||
if args.clear:
|
||||
self.device.execute("pm clear {}".format(args.package))
|
||||
@ -114,18 +148,24 @@ class RecordCommand(Command):
|
||||
|
||||
self.logger.info("Press Enter when you are ready to record...")
|
||||
raw_input("")
|
||||
command = "{} record -t 100000 -s {}".format(self.target_binary, revent_file)
|
||||
gamepad_flag = '-g ' if args.gamepad else ''
|
||||
command = "{} record {}-s {}".format(self.target_binary, gamepad_flag, revent_file)
|
||||
self.device.kick_off(command)
|
||||
|
||||
self.logger.info("Press Enter when you have finished recording...")
|
||||
raw_input("")
|
||||
self.device.killall("revent")
|
||||
|
||||
if args.capture_screen:
|
||||
self.logger.info("Recording screen capture")
|
||||
self.device.capture_screen(args.output or os.getcwdu())
|
||||
self.device.killall("revent", signal.SIGINT)
|
||||
self.logger.info("Waiting for revent to finish")
|
||||
while self.device.get_pids_of("revent"):
|
||||
pass
|
||||
self.logger.info("Pulling files from device")
|
||||
self.device.pull_file(revent_file, args.output or os.getcwdu())
|
||||
|
||||
|
||||
class ReplayCommand(RecordCommand):
|
||||
class ReplayCommand(ReventCommand):
|
||||
|
||||
name = 'replay'
|
||||
description = '''Replay a revent recording
|
||||
@ -154,8 +194,13 @@ class ReplayCommand(RecordCommand):
|
||||
self.logger.info("Starting {}".format(args.package))
|
||||
self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.format(args.package))
|
||||
|
||||
self.logger.info("Replaying recording")
|
||||
command = "{} replay {}".format(self.target_binary, revent_file)
|
||||
self.device.execute(command)
|
||||
recording = ReventRecording(args.revent)
|
||||
timeout = ceil(recording.duration) + 30
|
||||
recording.close()
|
||||
self.device.execute(command, timeout=timeout,
|
||||
as_root=(recording.mode == GAMEPAD_MODE))
|
||||
self.logger.info("Finished replay")
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ import shutil
|
||||
|
||||
import wlauto
|
||||
from wlauto import Command, settings
|
||||
from wlauto.exceptions import ConfigError
|
||||
from wlauto.core.agenda import Agenda
|
||||
from wlauto.core.execution import Executor
|
||||
from wlauto.utils.log import add_log_file
|
||||
@ -76,6 +77,11 @@ class RunCommand(Command):
|
||||
agenda = Agenda(args.agenda)
|
||||
settings.agenda = args.agenda
|
||||
shutil.copy(args.agenda, settings.meta_directory)
|
||||
|
||||
if len(agenda.workloads) == 0:
|
||||
raise ConfigError("No workloads specified")
|
||||
elif '.' in args.agenda or os.sep in args.agenda:
|
||||
raise ConfigError('Agenda "{}" does not exist.'.format(args.agenda))
|
||||
else:
|
||||
self.logger.debug('{} is not a file; assuming workload name.'.format(args.agenda))
|
||||
agenda = Agenda()
|
||||
|
@ -111,4 +111,3 @@ def format_extension_parameters(extension, out, width, shift=4):
|
||||
param_texts.append(indent(param_text, shift))
|
||||
|
||||
out.write(format_column('\n'.join(param_texts), width))
|
||||
|
||||
|
@ -2,23 +2,30 @@ package ${package_name};
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
// Import the uiautomator libraries
|
||||
import com.android.uiautomator.core.UiObject;
|
||||
import com.android.uiautomator.core.UiObjectNotFoundException;
|
||||
import com.android.uiautomator.core.UiScrollable;
|
||||
import com.android.uiautomator.core.UiSelector;
|
||||
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
|
||||
import android.support.test.uiautomator.UiObject;
|
||||
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||
import android.support.test.uiautomator.UiScrollable;
|
||||
import android.support.test.uiautomator.UiSelector;
|
||||
|
||||
|
||||
import com.arm.wlauto.uiauto.BaseUiAutomation;
|
||||
|
||||
public class UiAutomation extends BaseUiAutomation {
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UiAutomation extends BaseUiAutomation {
|
||||
|
||||
public static String TAG = "${name}";
|
||||
|
||||
public void runUiAutomation() throws Exception {
|
||||
@Test
|
||||
public void runUiAutomation() throws Exception {
|
||||
initialize_instrumentation();
|
||||
// UI Automation code goes here
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ class ${class_name}(AndroidBenchmark):
|
||||
|
||||
parameters = [
|
||||
# Workload parameters go here e.g.
|
||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
description='This is an example parameter')
|
||||
]
|
||||
|
||||
|
@ -14,7 +14,7 @@ class ${class_name}(AndroidUiAutoBenchmark):
|
||||
|
||||
parameters = [
|
||||
# Workload parameters go here e.g.
|
||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
description='This is an example parameter')
|
||||
]
|
||||
|
||||
|
@ -8,7 +8,7 @@ class ${class_name}(Workload):
|
||||
|
||||
parameters = [
|
||||
# Workload parameters go here e.g.
|
||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
description='This is an example parameter')
|
||||
]
|
||||
|
||||
|
12
wlauto/commands/templates/uiauto_AndroidManifest.xml
Normal file
12
wlauto/commands/templates/uiauto_AndroidManifest.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="${package_name}"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
|
||||
<instrumentation
|
||||
android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="${package_name}"/>
|
||||
|
||||
</manifest>
|
33
wlauto/commands/templates/uiauto_build.gradle
Normal file
33
wlauto/commands/templates/uiauto_build.gradle
Normal file
@ -0,0 +1,33 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 18
|
||||
buildToolsVersion '25.0.0'
|
||||
defaultConfig {
|
||||
applicationId "${package_name}"
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 25
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.outputFile = file("$$project.buildDir/apk/${package_name}.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
compile 'com.android.support.test:runner:0.5'
|
||||
compile 'com.android.support.test:rules:0.5'
|
||||
compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||
compile(name: 'uiauto', ext: 'aar')
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
39
wlauto/commands/templates/uiauto_build_script
Normal file
39
wlauto/commands/templates/uiauto_build_script
Normal file
@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# CD into build dir if possible - allows building from any directory
|
||||
script_path='.'
|
||||
if `readlink -f $$0 &>/dev/null`; then
|
||||
script_path=`readlink -f $$0 2>/dev/null`
|
||||
fi
|
||||
script_dir=`dirname $$script_path`
|
||||
cd $$script_dir
|
||||
|
||||
# Ensure gradelw exists before starting
|
||||
if [[ ! -f gradlew ]]; then
|
||||
echo 'gradlew file not found! Check that you are in the right directory.'
|
||||
exit 9
|
||||
fi
|
||||
|
||||
# Copy base class library from wlauto dist
|
||||
libs_dir=app/libs
|
||||
base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.aar')"`
|
||||
mkdir -p $$libs_dir
|
||||
cp $$base_classes $$libs_dir
|
||||
|
||||
# Build and return appropriate exit code if failed
|
||||
# gradle build
|
||||
./gradlew clean :app:assembleDebug
|
||||
exit_code=$$?
|
||||
if [[ $$exit_code -ne 0 ]]; then
|
||||
echo "ERROR: 'gradle build' exited with code $$exit_code"
|
||||
exit $$exit_code
|
||||
fi
|
||||
|
||||
# If successful move APK file to workload folder (overwrite previous)
|
||||
rm -f ../$package_name
|
||||
if [[ -f app/build/apk/$package_name.apk ]]; then
|
||||
cp app/build/apk/$package_name.apk ../$package_name.apk
|
||||
else
|
||||
echo 'ERROR: UiAutomator apk could not be found!'
|
||||
exit 9
|
||||
fi
|
23
wlauto/commands/templates/uiauto_template/build.gradle
Normal file
23
wlauto/commands/templates/uiauto_template/build.gradle
Normal file
@ -0,0 +1,23 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
BIN
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
wlauto/commands/templates/uiauto_template/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Wed May 03 15:42:44 BST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
160
wlauto/commands/templates/uiauto_template/gradlew
vendored
Executable file
160
wlauto/commands/templates/uiauto_template/gradlew
vendored
Executable file
@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
wlauto/commands/templates/uiauto_template/gradlew.bat
vendored
Normal file
90
wlauto/commands/templates/uiauto_template/gradlew.bat
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
@ -0,0 +1 @@
|
||||
include ':app'
|
@ -8,7 +8,7 @@ class ${class_name}(UiAutomatorWorkload):
|
||||
|
||||
parameters = [
|
||||
# Workload parameters go here e.g.
|
||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||
description='This is an example parameter')
|
||||
]
|
||||
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -21,12 +21,16 @@ import time
|
||||
import tempfile
|
||||
import shutil
|
||||
import threading
|
||||
import json
|
||||
import xml.dom.minidom
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
from wlauto.core.extension import Parameter
|
||||
from wlauto.common.resources import Executable
|
||||
from wlauto.core.resource import NO_ONE
|
||||
from wlauto.common.linux.device import BaseLinuxDevice, PsEntry
|
||||
from wlauto.exceptions import DeviceError, WorkerThreadError, TimeoutError, DeviceNotRespondingError
|
||||
from wlauto.utils.misc import convert_new_lines
|
||||
from wlauto.utils.misc import convert_new_lines, ABI_MAP, commonprefix
|
||||
from wlauto.utils.types import boolean, regex
|
||||
from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices,
|
||||
adb_command, AndroidProperties, ANDROID_VERSION_MAP)
|
||||
@ -49,9 +53,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
description='The unique ID of the device as output by "adb devices".'),
|
||||
Parameter('android_prompt', kind=regex, default=re.compile('^.*(shell|root)@.*:/\S* [#$] ', re.MULTILINE),
|
||||
description='The format of matching the shell prompt in Android.'),
|
||||
Parameter('working_directory', default='/sdcard/wa-working',
|
||||
description='Directory that will be used WA on the device for output files etc.'),
|
||||
Parameter('binaries_directory', default='/data/local/tmp', override=True,
|
||||
Parameter('working_directory', default='/sdcard/wa-working', override=True),
|
||||
Parameter('binaries_directory', default='/data/local/tmp/wa-bin', override=True,
|
||||
description='Location of binaries on the device.'),
|
||||
Parameter('package_data_directory', default='/data/data',
|
||||
description='Location of of data for an installed package (APK).'),
|
||||
@ -105,19 +108,34 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
|
||||
@property
|
||||
def abi(self):
|
||||
return self.getprop()['ro.product.cpu.abi'].split('-')[0]
|
||||
val = self.getprop()['ro.product.cpu.abi'].split('-')[0]
|
||||
for abi, architectures in ABI_MAP.iteritems():
|
||||
if val in architectures:
|
||||
return abi
|
||||
return val
|
||||
|
||||
@property
|
||||
def supported_eabi(self):
|
||||
def supported_abi(self):
|
||||
props = self.getprop()
|
||||
result = [props['ro.product.cpu.abi']]
|
||||
if 'ro.product.cpu.abi2' in props:
|
||||
result.append(props['ro.product.cpu.abi2'])
|
||||
if 'ro.product.cpu.abilist' in props:
|
||||
for eabi in props['ro.product.cpu.abilist'].split(','):
|
||||
if eabi not in result:
|
||||
result.append(eabi)
|
||||
return result
|
||||
for abi in props['ro.product.cpu.abilist'].split(','):
|
||||
if abi not in result:
|
||||
result.append(abi)
|
||||
|
||||
mapped_result = []
|
||||
for supported_abi in result:
|
||||
for abi, architectures in ABI_MAP.iteritems():
|
||||
found = False
|
||||
if supported_abi in architectures and abi not in mapped_result:
|
||||
mapped_result.append(abi)
|
||||
found = True
|
||||
break
|
||||
if not found and supported_abi not in mapped_result:
|
||||
mapped_result.append(supported_abi)
|
||||
return mapped_result
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AndroidDevice, self).__init__(**kwargs)
|
||||
@ -193,6 +211,7 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
self._is_ready = True
|
||||
|
||||
def initialize(self, context):
|
||||
self.sqlite = self.deploy_sqlite3(context) # pylint: disable=attribute-defined-outside-init
|
||||
if self.is_rooted:
|
||||
self.disable_screen_lock()
|
||||
self.disable_selinux()
|
||||
@ -258,6 +277,24 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
return line.split('=', 1)[1]
|
||||
return None
|
||||
|
||||
def get_installed_package_abi(self, package):
|
||||
"""
|
||||
Returns the primary abi of the specified package if it is installed
|
||||
on the device, or ``None`` otherwise.
|
||||
"""
|
||||
output = self.execute('dumpsys package {}'.format(package))
|
||||
val = None
|
||||
for line in convert_new_lines(output).split('\n'):
|
||||
if 'primaryCpuAbi' in line:
|
||||
val = line.split('=', 1)[1]
|
||||
break
|
||||
if val == 'null':
|
||||
return None
|
||||
for abi, architectures in ABI_MAP.iteritems():
|
||||
if val in architectures:
|
||||
return abi
|
||||
return val
|
||||
|
||||
def list_packages(self):
|
||||
"""
|
||||
List packages installed on the device.
|
||||
@ -338,7 +375,7 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
|
||||
def delete_file(self, filepath, as_root=False): # pylint: disable=W0221
|
||||
self._check_ready()
|
||||
adb_shell(self.adb_name, "rm '{}'".format(filepath), as_root=as_root, timeout=self.default_timeout)
|
||||
adb_shell(self.adb_name, "rm -rf '{}'".format(filepath), as_root=as_root, timeout=self.default_timeout)
|
||||
|
||||
def file_exists(self, filepath):
|
||||
self._check_ready()
|
||||
@ -346,18 +383,26 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
timeout=self.default_timeout)
|
||||
return bool(int(output))
|
||||
|
||||
def install(self, filepath, timeout=default_timeout, with_name=None): # pylint: disable=W0221
|
||||
def install(self, filepath, timeout=default_timeout, with_name=None, replace=False): # pylint: disable=W0221
|
||||
ext = os.path.splitext(filepath)[1].lower()
|
||||
if ext == '.apk':
|
||||
return self.install_apk(filepath, timeout)
|
||||
return self.install_apk(filepath, timeout, replace)
|
||||
else:
|
||||
return self.install_executable(filepath, with_name)
|
||||
|
||||
def install_apk(self, filepath, timeout=default_timeout): # pylint: disable=W0221
|
||||
def install_apk(self, filepath, timeout=300, replace=False, allow_downgrade=False): # pylint: disable=W0221
|
||||
self._check_ready()
|
||||
ext = os.path.splitext(filepath)[1].lower()
|
||||
if ext == '.apk':
|
||||
return adb_command(self.adb_name, "install {}".format(filepath), timeout=timeout)
|
||||
flags = []
|
||||
if replace:
|
||||
flags.append('-r') # Replace existing APK
|
||||
if allow_downgrade:
|
||||
flags.append('-d') # Install the APK even if a newer version is already installed
|
||||
if self.get_sdk_version() >= 23:
|
||||
flags.append('-g') # Grant all runtime permissions
|
||||
self.logger.debug("Replace APK = {}, ADB flags = '{}'".format(replace, ' '.join(flags)))
|
||||
return adb_command(self.adb_name, "install {} '{}'".format(' '.join(flags), filepath), timeout=timeout)
|
||||
else:
|
||||
raise DeviceError('Can\'t install {}: unsupported format.'.format(filepath))
|
||||
|
||||
@ -442,22 +487,20 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
else:
|
||||
return adb_shell(self.adb_name, command, timeout, check_exit_code, as_root)
|
||||
|
||||
def kick_off(self, command):
|
||||
def kick_off(self, command, as_root=None):
|
||||
"""
|
||||
Like execute but closes adb session and returns immediately, leaving the command running on the
|
||||
device (this is different from execute(background=True) which keeps adb connection open and returns
|
||||
a subprocess object).
|
||||
|
||||
.. note:: This relies on busybox's nohup applet and so won't work on unrooted devices.
|
||||
|
||||
Added in version 2.1.4
|
||||
|
||||
"""
|
||||
if not self.is_rooted:
|
||||
raise DeviceError('kick_off uses busybox\'s nohup applet and so can only be run a rooted device.')
|
||||
if as_root is None:
|
||||
as_root = self.is_rooted
|
||||
try:
|
||||
command = 'cd {} && busybox nohup {}'.format(self.working_directory, command)
|
||||
output = self.execute(command, timeout=1, as_root=True)
|
||||
command = 'cd {} && {} nohup {}'.format(self.working_directory, self.busybox, command)
|
||||
output = self.execute(command, timeout=1, as_root=as_root)
|
||||
except TimeoutError:
|
||||
pass
|
||||
else:
|
||||
@ -465,8 +508,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
|
||||
def get_pids_of(self, process_name):
|
||||
"""Returns a list of PIDs of all processes with the specified name."""
|
||||
result = self.execute('ps | {} grep {}'.format(self.busybox, process_name),
|
||||
check_exit_code=False).strip()
|
||||
result = (self.execute('ps | {} grep {}'.format(self.busybox, process_name),
|
||||
check_exit_code=False) or '').strip()
|
||||
if result and 'not found' not in result:
|
||||
return [int(x.split()[1]) for x in result.split('\n')]
|
||||
else:
|
||||
@ -505,17 +548,17 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
def _get_android_properties(self, context):
|
||||
props = {}
|
||||
props['android_id'] = self.get_android_id()
|
||||
buildprop_file = os.path.join(context.host_working_directory, 'build.prop')
|
||||
if not os.path.isfile(buildprop_file):
|
||||
self.pull_file('/system/build.prop', context.host_working_directory)
|
||||
self._update_build_properties(buildprop_file, props)
|
||||
context.add_run_artifact('build_properties', buildprop_file, 'export')
|
||||
self._update_build_properties(props)
|
||||
|
||||
dumpsys_target_file = self.path.join(self.working_directory, 'window.dumpsys')
|
||||
dumpsys_host_file = os.path.join(context.host_working_directory, 'window.dumpsys')
|
||||
self.execute('{} > {}'.format('dumpsys window', dumpsys_target_file))
|
||||
self.pull_file(dumpsys_target_file, dumpsys_host_file)
|
||||
context.add_run_artifact('dumpsys_window', dumpsys_host_file, 'meta')
|
||||
with open(dumpsys_host_file, 'w') as wfh:
|
||||
wfh.write(self.execute('dumpsys window'))
|
||||
context.add_run_artifact('dumpsys_window', dumpsys_host_file, 'meta')
|
||||
|
||||
prop_file = os.path.join(context.host_working_directory, 'android-props.json')
|
||||
with open(prop_file, 'w') as wfh:
|
||||
json.dump(props, wfh)
|
||||
context.add_run_artifact('android_properties', prop_file, 'export')
|
||||
return props
|
||||
|
||||
def getprop(self, prop=None):
|
||||
@ -529,6 +572,11 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
return props[prop]
|
||||
return props
|
||||
|
||||
def deploy_sqlite3(self, context):
|
||||
host_file = context.resolver.get(Executable(NO_ONE, self.abi, 'sqlite3'))
|
||||
target_file = self.install_if_needed(host_file)
|
||||
return target_file
|
||||
|
||||
# Android-specific methods. These either rely on specifics of adb or other
|
||||
# Android-only concepts in their interface and/or implementation.
|
||||
|
||||
@ -603,6 +651,17 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
self.pull_file(on_device_file, filepath)
|
||||
self.delete_file(on_device_file)
|
||||
|
||||
def capture_ui_hierarchy(self, filepath):
|
||||
"""Captures the current view hierarchy into the specified file in a XML format."""
|
||||
on_device_file = self.path.join(self.working_directory, 'screen_capture.xml')
|
||||
self.execute('uiautomator dump {}'.format(on_device_file))
|
||||
self.pull_file(on_device_file, filepath)
|
||||
self.delete_file(on_device_file)
|
||||
|
||||
parsed_xml = xml.dom.minidom.parse(filepath)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(parsed_xml.toprettyxml())
|
||||
|
||||
def is_screen_on(self):
|
||||
"""Returns ``True`` if the device screen is currently on, ``False`` otherwise."""
|
||||
output = self.execute('dumpsys power')
|
||||
@ -629,7 +688,15 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
"""
|
||||
lockdb = '/data/system/locksettings.db'
|
||||
sqlcommand = "update locksettings set value='0' where name='screenlock.disabled';"
|
||||
self.execute('sqlite3 {} "{}"'.format(lockdb, sqlcommand), as_root=True)
|
||||
f = tempfile.NamedTemporaryFile()
|
||||
try:
|
||||
f.write('{} {} "{}"'.format(self.sqlite, lockdb, sqlcommand))
|
||||
f.flush()
|
||||
on_device_executable = self.install_executable(f.name,
|
||||
with_name="disable_screen_lock")
|
||||
finally:
|
||||
f.close()
|
||||
self.execute(on_device_executable, as_root=True)
|
||||
|
||||
def disable_selinux(self):
|
||||
# This may be invoked from intialize() so we can't use execute() or the
|
||||
@ -649,17 +716,41 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
# Internal methods: do not use outside of the class.
|
||||
def refresh_device_files(self, file_list):
|
||||
"""
|
||||
Depending on the devices android version and root status, determine the
|
||||
appropriate method of forcing a re-index of the mediaserver cache for a given
|
||||
list of files.
|
||||
"""
|
||||
if self.device.is_rooted or self.device.get_sdk_version() < 24: # MM and below
|
||||
common_path = commonprefix(file_list, sep=self.device.path.sep)
|
||||
self.broadcast_media_mounted(common_path, self.device.is_rooted)
|
||||
else:
|
||||
for f in file_list:
|
||||
self.broadcast_media_scan_file(f)
|
||||
|
||||
def _update_build_properties(self, filepath, props):
|
||||
def broadcast_media_scan_file(self, filepath):
|
||||
"""
|
||||
Force a re-index of the mediaserver cache for the specified file.
|
||||
"""
|
||||
command = 'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://'
|
||||
self.execute(command + filepath)
|
||||
|
||||
def broadcast_media_mounted(self, dirpath, as_root=False):
|
||||
"""
|
||||
Force a re-index of the mediaserver cache for the specified directory.
|
||||
"""
|
||||
command = 'am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://'
|
||||
self.execute(command + dirpath, as_root=as_root)
|
||||
|
||||
# Internal methods: do not use outside of the class.
|
||||
def _update_build_properties(self, props):
|
||||
try:
|
||||
with open(filepath) as fh:
|
||||
for line in fh:
|
||||
line = re.sub(r'#.*', '', line).strip()
|
||||
if not line:
|
||||
continue
|
||||
key, value = line.split('=', 1)
|
||||
props[key] = value
|
||||
regex = re.compile(r'\[([^\]]+)\]\s*:\s*\[([^\]]+)\]')
|
||||
for match in regex.finditer(self.execute("getprop")):
|
||||
key = match.group(1).strip()
|
||||
value = match.group(2).strip()
|
||||
props[key] = value
|
||||
except ValueError:
|
||||
self.logger.warning('Could not parse build.prop.')
|
||||
|
||||
|
@ -34,3 +34,13 @@ class JarFile(FileResource):
|
||||
class ApkFile(FileResource):
|
||||
|
||||
name = 'apk'
|
||||
|
||||
def __init__(self, owner, platform=None, uiauto=False, package=None):
|
||||
super(ApkFile, self).__init__(owner)
|
||||
self.platform = platform
|
||||
self.uiauto = uiauto
|
||||
self.package = package
|
||||
|
||||
def __str__(self):
|
||||
apk_type = 'uiautomator ' if self.uiauto else ''
|
||||
return '<{}\'s {} {}APK>'.format(self.owner, self.platform, apk_type)
|
||||
|
BIN
wlauto/common/android/uiauto.aar
Normal file
BIN
wlauto/common/android/uiauto.aar
Normal file
Binary file not shown.
586
wlauto/common/android/workload.py
Normal file → Executable file
586
wlauto/common/android/workload.py
Normal file → Executable file
@ -17,27 +17,37 @@ import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from wlauto.core.extension import Parameter
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from wlauto.core.extension import Parameter, ExtensionMeta, ListCollection
|
||||
from wlauto.core.workload import Workload
|
||||
from wlauto.core.resource import NO_ONE
|
||||
from wlauto.common.resources import ExtensionAsset, Executable
|
||||
from wlauto.exceptions import WorkloadError, ResourceError, ConfigError
|
||||
from wlauto.utils.android import ApkInfo, ANDROID_NORMAL_PERMISSIONS
|
||||
from wlauto.utils.types import boolean
|
||||
import wlauto.common.android.resources
|
||||
from wlauto.common.android.resources import ApkFile
|
||||
from wlauto.common.resources import ExtensionAsset, File
|
||||
from wlauto.exceptions import WorkloadError, ResourceError, DeviceError
|
||||
from wlauto.utils.android import (ApkInfo, ANDROID_NORMAL_PERMISSIONS,
|
||||
ANDROID_UNCHANGEABLE_PERMISSIONS, UNSUPPORTED_PACKAGES)
|
||||
from wlauto.utils.types import boolean, ParameterDict
|
||||
import wlauto.utils.statedetect as state_detector
|
||||
from wlauto.common.linux.workload import ReventWorkload
|
||||
|
||||
|
||||
DELAY = 5
|
||||
|
||||
|
||||
# Due to the way `super` works you have to call it at every level but WA executes some
|
||||
# methods conditionally and so has to call them directly via the class, this breaks super
|
||||
# and causes it to run things mutiple times ect. As a work around for this untill workloads
|
||||
# are reworked everything that subclasses workload calls parent methods explicitly
|
||||
|
||||
|
||||
class UiAutomatorWorkload(Workload):
|
||||
"""
|
||||
Base class for all workloads that rely on a UI Automator JAR file.
|
||||
Base class for all workloads that rely on a UI Automator APK file.
|
||||
|
||||
This class should be subclassed by workloads that rely on android UiAutomator
|
||||
to work. This class handles transferring the UI Automator JAR file to the device
|
||||
and invoking it to run the workload. By default, it will look for the JAR file in
|
||||
the same directory as the .py file for the workload (this can be changed by overriding
|
||||
to work. This class handles installing the UI Automator APK to the device
|
||||
and invoking it to run the workload. By default, it will look for the ``*.apk`` file
|
||||
in the same directory as the .py file for the workload (this can be changed by overriding
|
||||
the ``uiauto_file`` property in the subclassing workload).
|
||||
|
||||
To inintiate UI Automation, the fully-qualified name of the Java class and the
|
||||
@ -49,7 +59,7 @@ class UiAutomatorWorkload(Workload):
|
||||
match what is expected, or you could override ``uiauto_package``, ``uiauto_class`` and
|
||||
``uiauto_method`` class attributes with the value that match your Java code.
|
||||
|
||||
You can also pass parameters to the JAR file. To do this add the parameters to
|
||||
You can also pass parameters to the APK file. To do this add the parameters to
|
||||
``self.uiauto_params`` dict inside your class's ``__init__`` or ``setup`` methods.
|
||||
|
||||
"""
|
||||
@ -58,38 +68,43 @@ class UiAutomatorWorkload(Workload):
|
||||
|
||||
uiauto_package = ''
|
||||
uiauto_class = 'UiAutomation'
|
||||
uiauto_method = 'runUiAutomation'
|
||||
|
||||
uiauto_method = 'android.support.test.runner.AndroidJUnitRunner'
|
||||
# Can be overidden by subclasses to adjust to run time of specific
|
||||
# benchmarks.
|
||||
run_timeout = 4 * 60 # seconds
|
||||
run_timeout = 10 * 60 # seconds
|
||||
uninstall_uiauto_apk = True
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs): # pylint: disable=W0613
|
||||
if _call_super:
|
||||
super(UiAutomatorWorkload, self).__init__(device, **kwargs)
|
||||
Workload.__init__(self, device, **kwargs)
|
||||
self.uiauto_file = None
|
||||
self.device_uiauto_file = None
|
||||
self.command = None
|
||||
self.uiauto_params = {}
|
||||
self.uiauto_params = ParameterDict()
|
||||
|
||||
def init_resources(self, context):
|
||||
self.uiauto_file = context.resolver.get(wlauto.common.android.resources.JarFile(self))
|
||||
self.uiauto_file = context.resolver.get(ApkFile(self, uiauto=True))
|
||||
if not self.uiauto_file:
|
||||
raise ResourceError('No UI automation JAR file found for workload {}.'.format(self.name))
|
||||
self.device_uiauto_file = self.device.path.join(self.device.working_directory,
|
||||
os.path.basename(self.uiauto_file))
|
||||
raise ResourceError('No UI automation APK file found for workload {}.'.format(self.name))
|
||||
|
||||
if not self.uiauto_package:
|
||||
self.uiauto_package = os.path.splitext(os.path.basename(self.uiauto_file))[0]
|
||||
|
||||
def setup(self, context):
|
||||
method_string = '{}.{}#{}'.format(self.uiauto_package, self.uiauto_class, self.uiauto_method)
|
||||
Workload.setup(self, context)
|
||||
params_dict = self.uiauto_params
|
||||
params_dict['workdir'] = self.device.working_directory
|
||||
params = ''
|
||||
for k, v in self.uiauto_params.iteritems():
|
||||
params += ' -e {} {}'.format(k, v)
|
||||
self.command = 'uiautomator runtest {}{} -c {}'.format(self.device_uiauto_file, params, method_string)
|
||||
self.device.push_file(self.uiauto_file, self.device_uiauto_file)
|
||||
for k, v in self.uiauto_params.iter_encoded_items():
|
||||
params += ' -e {} "{}"'.format(k, v)
|
||||
|
||||
if self.device.package_is_installed(self.uiauto_package):
|
||||
self.device.uninstall(self.uiauto_package)
|
||||
self.device.install_apk(self.uiauto_file)
|
||||
|
||||
instrumention_string = 'am instrument -w -r {} -e class {}.{} {}/{}'
|
||||
self.command = instrumention_string.format(params, self.uiauto_package,
|
||||
self.uiauto_class, self.uiauto_package,
|
||||
self.uiauto_method)
|
||||
self.device.killall('uiautomator')
|
||||
|
||||
def run(self, context):
|
||||
@ -104,11 +119,12 @@ class UiAutomatorWorkload(Workload):
|
||||
pass
|
||||
|
||||
def teardown(self, context):
|
||||
self.device.delete_file(self.device_uiauto_file)
|
||||
if self.uninstall_uiauto_apk:
|
||||
self.device.uninstall(self.uiauto_package)
|
||||
|
||||
def validate(self):
|
||||
if not self.uiauto_file:
|
||||
raise WorkloadError('No UI automation JAR file found for workload {}.'.format(self.name))
|
||||
raise WorkloadError('No UI automation APK file found for workload {}.'.format(self.name))
|
||||
if not self.uiauto_package:
|
||||
raise WorkloadError('No UI automation package specified for workload {}.'.format(self.name))
|
||||
|
||||
@ -122,10 +138,16 @@ class ApkWorkload(Workload):
|
||||
:package: The package name of the app. This is usually a Java-style name of the form
|
||||
``com.companyname.appname``.
|
||||
:activity: This is the initial activity of the app. This will be used to launch the
|
||||
app during the setup.
|
||||
app during the setup. Many applications do not specify a launch activity so
|
||||
this may be left blank if necessary.
|
||||
:view: The class of the main view pane of the app. This needs to be defined in order
|
||||
to collect SurfaceFlinger-derived statistics (such as FPS) for the app, but
|
||||
may otherwise be left as ``None``.
|
||||
:launch_main: If ``False``, the default activity will not be launched (during setup),
|
||||
allowing workloads to start the app with an intent of their choice in
|
||||
the run step. This is useful for apps without a launchable default/main
|
||||
activity or those where it cannot be launched without intent data (which
|
||||
is provided at the run phase).
|
||||
:install_timeout: Timeout for the installation of the APK. This may vary wildly based on
|
||||
the size and nature of a specific APK, and so should be defined on
|
||||
per-workload basis.
|
||||
@ -135,6 +157,9 @@ class ApkWorkload(Workload):
|
||||
so, as with all timeouts, so leeway must be included in
|
||||
the specified value.
|
||||
|
||||
:min_apk_version: The minimum supported apk version for this workload. May be ``None``.
|
||||
:max_apk_version: The maximum supported apk version for this workload. May be ``None``.
|
||||
|
||||
.. note:: Both package and activity for a workload may be obtained from the APK using
|
||||
the ``aapt`` tool that comes with the ADT (Android Developemnt Tools) bundle.
|
||||
|
||||
@ -142,91 +167,241 @@ class ApkWorkload(Workload):
|
||||
package = None
|
||||
activity = None
|
||||
view = None
|
||||
min_apk_version = None
|
||||
max_apk_version = None
|
||||
supported_platforms = ['android']
|
||||
launch_main = True
|
||||
|
||||
parameters = [
|
||||
Parameter('install_timeout', kind=int, default=300,
|
||||
description='Timeout for the installation of the apk.'),
|
||||
Parameter('check_apk', kind=boolean, default=True,
|
||||
description='''
|
||||
Discover the APK for this workload on the host, and check that
|
||||
the version matches the one on device (if already installed).
|
||||
When set to True the APK file on the host will be prefered if
|
||||
it is a valid version and ABI, if not it will fall back to the
|
||||
version on the targer. When set to False the target version is
|
||||
prefered.
|
||||
'''),
|
||||
Parameter('force_install', kind=boolean, default=False,
|
||||
description='''
|
||||
Always re-install the APK, even if matching version is found
|
||||
on already installed on the device.
|
||||
Always re-install the APK, even if matching version is found already installed
|
||||
on the device. Runs ``adb install -r`` to ensure existing APK is replaced. When
|
||||
this is set, check_apk is ignored.
|
||||
'''),
|
||||
Parameter('uninstall_apk', kind=boolean, default=False,
|
||||
description='If ``True``, will uninstall workload\'s APK as part of teardown.'),
|
||||
Parameter('exact_abi', kind=bool, default=False,
|
||||
description='''
|
||||
If ``True``, workload will check that the APK matches the target
|
||||
device ABI, otherwise any APK found will be used.
|
||||
'''),
|
||||
Parameter('clear_data_on_reset', kind=bool, default=True,
|
||||
description="""
|
||||
If set to ``False``, this will prevent WA from clearing package
|
||||
data for this workload prior to running it.
|
||||
"""),
|
||||
]
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs):
|
||||
if _call_super:
|
||||
super(ApkWorkload, self).__init__(device, **kwargs)
|
||||
Workload.__init__(self, device, **kwargs)
|
||||
self.apk_file = None
|
||||
self.apk_version = None
|
||||
self.logcat_log = None
|
||||
self.exact_apk_version = None
|
||||
|
||||
def init_resources(self, context):
|
||||
self.apk_file = context.resolver.get(wlauto.common.android.resources.ApkFile(self),
|
||||
version=getattr(self, 'version', None),
|
||||
strict=self.check_apk)
|
||||
|
||||
def validate(self):
|
||||
if self.check_apk:
|
||||
if not self.apk_file:
|
||||
raise WorkloadError('No APK file found for workload {}.'.format(self.name))
|
||||
else:
|
||||
if self.force_install:
|
||||
raise ConfigError('force_install cannot be "True" when check_apk is set to "False".')
|
||||
|
||||
def setup(self, context):
|
||||
self.initialize_package(context)
|
||||
self.start_activity()
|
||||
self.device.execute('am kill-all') # kill all *background* activities
|
||||
def setup(self, context): # pylint: disable=too-many-branches
|
||||
Workload.setup(self, context)
|
||||
self.setup_workload_apk(context)
|
||||
self.launch_application()
|
||||
self.kill_background()
|
||||
self.device.clear_logcat()
|
||||
|
||||
def initialize_package(self, context):
|
||||
installed_version = self.device.get_installed_package_version(self.package)
|
||||
if self.check_apk:
|
||||
self.initialize_with_host_apk(context, installed_version)
|
||||
else:
|
||||
if not installed_version:
|
||||
message = '''{} not found found on the device and check_apk is set to "False"
|
||||
so host version was not checked.'''
|
||||
raise WorkloadError(message.format(self.package))
|
||||
message = 'Version {} installed on device; skipping host APK check.'
|
||||
self.logger.debug(message.format(installed_version))
|
||||
self.reset(context)
|
||||
self.apk_version = installed_version
|
||||
def setup_workload_apk(self, context):
|
||||
# Get target version
|
||||
target_version = self.device.get_installed_package_version(self.package)
|
||||
if target_version:
|
||||
target_version = LooseVersion(target_version)
|
||||
self.logger.debug("Found version '{}' on target device".format(target_version))
|
||||
|
||||
# Get host version
|
||||
self.apk_file = context.resolver.get(ApkFile(self, self.device.abi,
|
||||
package=getattr(self, 'package', None)),
|
||||
version=getattr(self, 'version', None),
|
||||
variant_name=getattr(self, 'variant_name', None),
|
||||
strict=False)
|
||||
|
||||
# Get target abi
|
||||
target_abi = self.device.get_installed_package_abi(self.package)
|
||||
if target_abi:
|
||||
self.logger.debug("Found apk with primary abi '{}' on target device".format(target_abi))
|
||||
|
||||
# Get host version, primary abi is first, and then try to find supported.
|
||||
for abi in self.device.supported_abi:
|
||||
self.apk_file = context.resolver.get(ApkFile(self, abi,
|
||||
package=getattr(self, 'package', None)),
|
||||
version=getattr(self, 'version', None),
|
||||
variant_name=getattr(self, 'variant_name', None),
|
||||
strict=False)
|
||||
|
||||
# Stop if apk found, or if exact_abi is set only look for primary abi.
|
||||
if self.apk_file or self.exact_abi:
|
||||
break
|
||||
|
||||
host_version = self.check_host_version()
|
||||
self.verify_apk_version(target_version, target_abi, host_version)
|
||||
|
||||
def initialize_with_host_apk(self, context, installed_version):
|
||||
host_version = ApkInfo(self.apk_file).version_name
|
||||
if installed_version != host_version:
|
||||
if installed_version:
|
||||
message = '{} host version: {}, device version: {}; re-installing...'
|
||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
||||
host_version, installed_version))
|
||||
else:
|
||||
message = '{} host version: {}, not found on device; installing...'
|
||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
||||
host_version))
|
||||
self.force_install = True # pylint: disable=attribute-defined-outside-init
|
||||
else:
|
||||
message = '{} version {} found on both device and host.'
|
||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
||||
host_version))
|
||||
if self.force_install:
|
||||
if installed_version:
|
||||
self.device.uninstall(self.package)
|
||||
self.install_apk(context)
|
||||
self.force_install_apk(context, host_version)
|
||||
elif self.check_apk:
|
||||
self.prefer_host_apk(context, host_version, target_version)
|
||||
else:
|
||||
self.reset(context)
|
||||
self.apk_version = host_version
|
||||
self.prefer_target_apk(context, host_version, target_version)
|
||||
|
||||
def start_activity(self):
|
||||
output = self.device.execute('am start -W -n {}/{}'.format(self.package, self.activity))
|
||||
self.reset(context)
|
||||
self.apk_version = self.device.get_installed_package_version(self.package)
|
||||
context.add_classifiers(apk_version=self.apk_version)
|
||||
|
||||
def check_host_version(self):
|
||||
host_version = None
|
||||
if self.apk_file is not None:
|
||||
host_version = ApkInfo(self.apk_file).version_name
|
||||
if host_version:
|
||||
host_version = LooseVersion(host_version)
|
||||
self.logger.debug("Found version '{}' on host".format(host_version))
|
||||
return host_version
|
||||
|
||||
def verify_apk_version(self, target_version, target_abi, host_version):
|
||||
# Error if apk was not found anywhere
|
||||
if target_version is None and host_version is None:
|
||||
msg = "Could not find APK for '{}' on the host or target device"
|
||||
raise ResourceError(msg.format(self.name))
|
||||
|
||||
if self.exact_apk_version is not None:
|
||||
if self.exact_apk_version != target_version and self.exact_apk_version != host_version:
|
||||
msg = "APK version '{}' not found on the host '{}' or target '{}'"
|
||||
raise ResourceError(msg.format(self.exact_apk_version, host_version, target_version))
|
||||
|
||||
# Error if exact_abi and suitable apk not found on host and incorrect version on device
|
||||
if self.exact_abi and host_version is None:
|
||||
if target_abi != self.device.abi:
|
||||
msg = "APK abi '{}' not found on the host and target is '{}'"
|
||||
raise ResourceError(msg.format(self.device.abi, target_abi))
|
||||
|
||||
def launch_application(self):
|
||||
if self.launch_main:
|
||||
self.launch_package() # launch default activity without intent data
|
||||
|
||||
def kill_background(self):
|
||||
self.device.execute('am kill-all') # kill all *background* activities
|
||||
|
||||
def force_install_apk(self, context, host_version):
|
||||
if host_version is None:
|
||||
raise ResourceError("force_install is 'True' but could not find APK on the host")
|
||||
try:
|
||||
self.validate_version(host_version)
|
||||
except ResourceError as e:
|
||||
msg = "force_install is 'True' but the host version is invalid:\n\t{}"
|
||||
raise ResourceError(msg.format(str(e)))
|
||||
self.install_apk(context, replace=True)
|
||||
|
||||
def prefer_host_apk(self, context, host_version, target_version):
|
||||
msg = "check_apk is 'True' "
|
||||
if host_version is None:
|
||||
try:
|
||||
self.validate_version(target_version)
|
||||
except ResourceError as e:
|
||||
msg += "but the APK was not found on the host and the target version is invalid:\n\t{}"
|
||||
raise ResourceError(msg.format(str(e)))
|
||||
else:
|
||||
msg += "but the APK was not found on the host, using target version"
|
||||
self.logger.debug(msg)
|
||||
return
|
||||
try:
|
||||
self.validate_version(host_version)
|
||||
except ResourceError as e1:
|
||||
msg += "but the host APK version is invalid:\n\t{}\n"
|
||||
if target_version is None:
|
||||
msg += "The target does not have the app either"
|
||||
raise ResourceError(msg.format(str(e1)))
|
||||
try:
|
||||
self.validate_version(target_version)
|
||||
except ResourceError as e2:
|
||||
msg += "The target version is also invalid:\n\t{}"
|
||||
raise ResourceError(msg.format(str(e1), str(e2)))
|
||||
else:
|
||||
msg += "using the target version instead"
|
||||
self.logger.debug(msg.format(str(e1)))
|
||||
else: # Host version is valid
|
||||
if target_version is not None and target_version == host_version:
|
||||
msg += " and a matching version is alread on the device, doing nothing"
|
||||
self.logger.debug(msg)
|
||||
return
|
||||
msg += " and the host version is not on the target, installing APK"
|
||||
self.logger.debug(msg)
|
||||
self.install_apk(context, replace=True)
|
||||
|
||||
def prefer_target_apk(self, context, host_version, target_version):
|
||||
msg = "check_apk is 'False' "
|
||||
if target_version is None:
|
||||
try:
|
||||
self.validate_version(host_version)
|
||||
except ResourceError as e:
|
||||
msg += "but the app was not found on the target and the host version is invalid:\n\t{}"
|
||||
raise ResourceError(msg.format(str(e)))
|
||||
else:
|
||||
msg += "and the app was not found on the target, using host version"
|
||||
self.logger.debug(msg)
|
||||
self.install_apk(context)
|
||||
return
|
||||
try:
|
||||
self.validate_version(target_version)
|
||||
except ResourceError as e1:
|
||||
msg += "but the target app version is invalid:\n\t{}\n"
|
||||
if host_version is None:
|
||||
msg += "The host does not have the APK either"
|
||||
raise ResourceError(msg.format(str(e1)))
|
||||
try:
|
||||
self.validate_version(host_version)
|
||||
except ResourceError as e2:
|
||||
msg += "The host version is also invalid:\n\t{}"
|
||||
raise ResourceError(msg.format(str(e1), str(e2)))
|
||||
else:
|
||||
msg += "Using the host APK instead"
|
||||
self.logger.debug(msg.format(str(e1)))
|
||||
self.install_apk(context, replace=True)
|
||||
else:
|
||||
msg += "and a valid version of the app is already on the target, using target app"
|
||||
self.logger.debug(msg)
|
||||
|
||||
def validate_version(self, version):
|
||||
min_apk_version = getattr(self, 'min_apk_version', None)
|
||||
max_apk_version = getattr(self, 'max_apk_version', None)
|
||||
|
||||
if min_apk_version is not None and max_apk_version is not None:
|
||||
if version < LooseVersion(min_apk_version) or \
|
||||
version > LooseVersion(max_apk_version):
|
||||
msg = "version '{}' not supported. " \
|
||||
"Minimum version required: '{}', Maximum version known to work: '{}'"
|
||||
raise ResourceError(msg.format(version, min_apk_version, max_apk_version))
|
||||
|
||||
elif min_apk_version is not None:
|
||||
if version < LooseVersion(min_apk_version):
|
||||
msg = "version '{}' not supported. " \
|
||||
"Minimum version required: '{}'"
|
||||
raise ResourceError(msg.format(version, min_apk_version))
|
||||
|
||||
elif max_apk_version is not None:
|
||||
if version > LooseVersion(max_apk_version):
|
||||
msg = "version '{}' not supported. " \
|
||||
"Maximum version known to work: '{}'"
|
||||
raise ResourceError(msg.format(version, max_apk_version))
|
||||
|
||||
def launch_package(self):
|
||||
if not self.activity:
|
||||
output = self.device.execute('am start -W {}'.format(self.package))
|
||||
else:
|
||||
output = self.device.execute('am start -W -n {}/{}'.format(self.package, self.activity))
|
||||
if 'Error:' in output:
|
||||
self.device.execute('am force-stop {}'.format(self.package)) # this will dismiss any erro dialogs
|
||||
raise WorkloadError(output)
|
||||
@ -234,23 +409,32 @@ class ApkWorkload(Workload):
|
||||
|
||||
def reset(self, context): # pylint: disable=W0613
|
||||
self.device.execute('am force-stop {}'.format(self.package))
|
||||
self.device.execute('pm clear {}'.format(self.package))
|
||||
if self.clear_data_on_reset:
|
||||
self.device.execute('pm clear {}'.format(self.package))
|
||||
|
||||
# As of android API level 23, apps can request permissions at runtime,
|
||||
# this will grant all of them so requests do not pop up when running the app
|
||||
# This can also be done less "manually" during adb install using the -g flag
|
||||
if self.device.get_sdk_version() >= 23:
|
||||
self._grant_requested_permissions()
|
||||
|
||||
def install_apk(self, context):
|
||||
output = self.device.install(self.apk_file, self.install_timeout)
|
||||
def install_apk(self, context, replace=False):
|
||||
success = False
|
||||
if replace and self.device.package_is_installed(self.package):
|
||||
self.device.uninstall(self.package)
|
||||
output = self.device.install_apk(self.apk_file, timeout=self.install_timeout,
|
||||
replace=replace, allow_downgrade=True)
|
||||
if 'Failure' in output:
|
||||
if 'ALREADY_EXISTS' in output:
|
||||
self.logger.warn('Using already installed APK (did not unistall properly?)')
|
||||
self.reset(context)
|
||||
else:
|
||||
raise WorkloadError(output)
|
||||
else:
|
||||
self.logger.debug(output)
|
||||
success = True
|
||||
self.do_post_install(context)
|
||||
return success
|
||||
|
||||
def _grant_requested_permissions(self):
|
||||
dumpsys_output = self.device.execute(command="dumpsys package {}".format(self.package))
|
||||
@ -263,17 +447,29 @@ class ApkWorkload(Workload):
|
||||
for line in lines:
|
||||
if "android.permission." in line:
|
||||
permissions.append(line.split(":")[0].strip())
|
||||
else:
|
||||
# Matching either of these means the end of requested permissions section
|
||||
elif "install permissions:" in line or "runtime permissions:" in line:
|
||||
break
|
||||
|
||||
for permission in permissions:
|
||||
for permission in set(permissions):
|
||||
# "Normal" Permisions are automatically granted and cannot be changed
|
||||
permission_name = permission.rsplit('.', 1)[1]
|
||||
if permission_name not in ANDROID_NORMAL_PERMISSIONS:
|
||||
self.device.execute("pm grant {} {}".format(self.package, permission))
|
||||
# Some permissions are not allowed to be "changed"
|
||||
if permission_name not in ANDROID_UNCHANGEABLE_PERMISSIONS:
|
||||
# On some API 23+ devices, this may fail with a SecurityException
|
||||
# on previously granted permissions. In that case, just skip as it
|
||||
# is not fatal to the workload execution
|
||||
try:
|
||||
self.device.execute("pm grant {} {}".format(self.package, permission))
|
||||
except DeviceError as e:
|
||||
if "changeable permission" in e.message or "Unknown permission" in e.message:
|
||||
self.logger.debug(e)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def do_post_install(self, context):
|
||||
""" May be overwritten by dervied classes."""
|
||||
""" May be overwritten by derived classes."""
|
||||
pass
|
||||
|
||||
def run(self, context):
|
||||
@ -292,71 +488,8 @@ class ApkWorkload(Workload):
|
||||
if self.uninstall_apk:
|
||||
self.device.uninstall(self.package)
|
||||
|
||||
|
||||
AndroidBenchmark = ApkWorkload # backward compatibility
|
||||
|
||||
|
||||
class ReventWorkload(Workload):
|
||||
|
||||
default_setup_timeout = 5 * 60 # in seconds
|
||||
default_run_timeout = 10 * 60 # in seconds
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs):
|
||||
if _call_super:
|
||||
super(ReventWorkload, self).__init__(device, **kwargs)
|
||||
devpath = self.device.path
|
||||
self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent')
|
||||
self.on_device_setup_revent = devpath.join(self.device.working_directory, '{}.setup.revent'.format(self.device.name))
|
||||
self.on_device_run_revent = devpath.join(self.device.working_directory, '{}.run.revent'.format(self.device.name))
|
||||
self.setup_timeout = kwargs.get('setup_timeout', self.default_setup_timeout)
|
||||
self.run_timeout = kwargs.get('run_timeout', self.default_run_timeout)
|
||||
self.revent_setup_file = None
|
||||
self.revent_run_file = None
|
||||
|
||||
def init_resources(self, context):
|
||||
self.revent_setup_file = context.resolver.get(wlauto.common.android.resources.ReventFile(self, 'setup'))
|
||||
self.revent_run_file = context.resolver.get(wlauto.common.android.resources.ReventFile(self, 'run'))
|
||||
|
||||
def setup(self, context):
|
||||
self._check_revent_files(context)
|
||||
self.device.killall('revent')
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent)
|
||||
self.device.execute(command, timeout=self.setup_timeout)
|
||||
|
||||
def run(self, context):
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent)
|
||||
self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent)))
|
||||
self.device.execute(command, timeout=self.run_timeout)
|
||||
self.logger.debug('Replay completed.')
|
||||
|
||||
def update_result(self, context):
|
||||
pass
|
||||
|
||||
def teardown(self, context):
|
||||
self.device.delete_file(self.on_device_setup_revent)
|
||||
self.device.delete_file(self.on_device_run_revent)
|
||||
|
||||
def _check_revent_files(self, context):
|
||||
# check the revent binary
|
||||
revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
||||
if not os.path.isfile(revent_binary):
|
||||
message = '{} does not exist. '.format(revent_binary)
|
||||
message += 'Please build revent for your system and place it in that location'
|
||||
raise WorkloadError(message)
|
||||
if not self.revent_setup_file:
|
||||
# pylint: disable=too-few-format-args
|
||||
message = '{0}.setup.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name)
|
||||
raise WorkloadError(message)
|
||||
if not self.revent_run_file:
|
||||
# pylint: disable=too-few-format-args
|
||||
message = '{0}.run.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name)
|
||||
raise WorkloadError(message)
|
||||
|
||||
self.on_device_revent_binary = self.device.install_executable(revent_binary)
|
||||
self.device.push_file(self.revent_run_file, self.on_device_run_revent)
|
||||
self.device.push_file(self.revent_setup_file, self.on_device_setup_revent)
|
||||
|
||||
|
||||
class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
||||
|
||||
supported_platforms = ['android']
|
||||
@ -365,6 +498,11 @@ class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
||||
UiAutomatorWorkload.__init__(self, device, **kwargs)
|
||||
AndroidBenchmark.__init__(self, device, _call_super=False, **kwargs)
|
||||
|
||||
def initialize(self, context):
|
||||
UiAutomatorWorkload.initialize(self, context)
|
||||
AndroidBenchmark.initialize(self, context)
|
||||
self._check_unsupported_packages()
|
||||
|
||||
def init_resources(self, context):
|
||||
UiAutomatorWorkload.init_resources(self, context)
|
||||
AndroidBenchmark.init_resources(self, context)
|
||||
@ -381,6 +519,98 @@ class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
||||
UiAutomatorWorkload.teardown(self, context)
|
||||
AndroidBenchmark.teardown(self, context)
|
||||
|
||||
def _check_unsupported_packages(self):
|
||||
"""
|
||||
Check for any unsupported package versions and raise an
|
||||
exception if detected.
|
||||
|
||||
"""
|
||||
for package in UNSUPPORTED_PACKAGES:
|
||||
version = self.device.get_installed_package_version(package)
|
||||
if version is None:
|
||||
continue
|
||||
|
||||
if '-' in version:
|
||||
version = version.split('-')[0] # ignore abi version
|
||||
|
||||
if version in UNSUPPORTED_PACKAGES[package]:
|
||||
message = 'This workload does not support version "{}" of package "{}"'
|
||||
raise WorkloadError(message.format(version, package))
|
||||
|
||||
|
||||
class AndroidUxPerfWorkloadMeta(ExtensionMeta):
|
||||
to_propagate = ExtensionMeta.to_propagate + [('deployable_assets', str, ListCollection)]
|
||||
|
||||
|
||||
class AndroidUxPerfWorkload(AndroidUiAutoBenchmark):
|
||||
__metaclass__ = AndroidUxPerfWorkloadMeta
|
||||
|
||||
deployable_assets = []
|
||||
parameters = [
|
||||
Parameter('markers_enabled', kind=bool, default=False,
|
||||
description="""
|
||||
If ``True``, UX_PERF action markers will be emitted to logcat during
|
||||
the test run.
|
||||
"""),
|
||||
Parameter('clean_assets', kind=bool, default=False,
|
||||
description="""
|
||||
If ``True`` pushed assets will be deleted at the end of each iteration
|
||||
"""),
|
||||
Parameter('force_push_assets', kind=bool, default=False,
|
||||
description="""
|
||||
If ``True`` always push assets on each iteration, even if the
|
||||
assets already exists in the device path
|
||||
"""),
|
||||
]
|
||||
|
||||
def _path_on_device(self, fpath, dirname=None):
|
||||
if dirname is None:
|
||||
dirname = self.device.working_directory
|
||||
fname = os.path.basename(fpath)
|
||||
return self.device.path.join(dirname, fname)
|
||||
|
||||
def push_assets(self, context):
|
||||
pushed = False
|
||||
file_list = []
|
||||
for f in self.deployable_assets:
|
||||
fpath = context.resolver.get(File(self, f))
|
||||
device_path = self._path_on_device(fpath)
|
||||
if self.force_push_assets or not self.device.file_exists(device_path):
|
||||
self.device.push_file(fpath, device_path, timeout=300)
|
||||
file_list.append(device_path)
|
||||
pushed = True
|
||||
if pushed:
|
||||
self.device.refresh_device_files(file_list)
|
||||
|
||||
def delete_assets(self):
|
||||
if self.deployable_assets:
|
||||
file_list = []
|
||||
for f in self.deployable_assets:
|
||||
f = self._path_on_device(f)
|
||||
self.device.delete_file(f)
|
||||
file_list.append(f)
|
||||
self.device.delete_file(f)
|
||||
self.device.refresh_device_files(file_list)
|
||||
|
||||
def __init__(self, device, **kwargs):
|
||||
super(AndroidUxPerfWorkload, self).__init__(device, **kwargs)
|
||||
# Turn class attribute into instance attribute
|
||||
self.deployable_assets = list(self.deployable_assets)
|
||||
|
||||
def validate(self):
|
||||
super(AndroidUxPerfWorkload, self).validate()
|
||||
self.uiauto_params['package_name'] = self.package
|
||||
self.uiauto_params['markers_enabled'] = self.markers_enabled
|
||||
|
||||
def setup(self, context):
|
||||
super(AndroidUxPerfWorkload, self).setup(context)
|
||||
self.push_assets(context)
|
||||
|
||||
def teardown(self, context):
|
||||
super(AndroidUxPerfWorkload, self).teardown(context)
|
||||
if self.clean_assets:
|
||||
self.delete_assets()
|
||||
|
||||
|
||||
class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
"""
|
||||
@ -414,21 +644,22 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
view = 'SurfaceView'
|
||||
loading_time = 10
|
||||
supported_platforms = ['android']
|
||||
setup_required = True
|
||||
|
||||
parameters = [
|
||||
Parameter('install_timeout', default=500, override=True),
|
||||
Parameter('check_states', kind=bool, default=False, global_alias='check_game_states',
|
||||
description="""Use visual state detection to verify the state of the workload
|
||||
after setup and run"""),
|
||||
Parameter('assets_push_timeout', kind=int, default=500,
|
||||
description='Timeout used during deployment of the assets package (if there is one).'),
|
||||
Parameter('clear_data_on_reset', kind=bool, default=True,
|
||||
description="""
|
||||
If set to ``False``, this will prevent WA from clearing package
|
||||
data for this workload prior to running it.
|
||||
"""),
|
||||
]
|
||||
|
||||
def __init__(self, device, **kwargs): # pylint: disable=W0613
|
||||
ApkWorkload.__init__(self, device, **kwargs)
|
||||
ReventWorkload.__init__(self, device, _call_super=False, **kwargs)
|
||||
if self.check_states:
|
||||
state_detector.check_match_state_dependencies()
|
||||
self.logcat_process = None
|
||||
self.module_dir = os.path.dirname(sys.modules[self.__module__].__file__)
|
||||
self.revent_dir = os.path.join(self.module_dir, 'revent_files')
|
||||
@ -436,6 +667,8 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
def init_resources(self, context):
|
||||
ApkWorkload.init_resources(self, context)
|
||||
ReventWorkload.init_resources(self, context)
|
||||
if self.check_states:
|
||||
self._check_statedetection_files(context)
|
||||
|
||||
def setup(self, context):
|
||||
ApkWorkload.setup(self, context)
|
||||
@ -443,6 +676,10 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
time.sleep(self.loading_time)
|
||||
ReventWorkload.setup(self, context)
|
||||
|
||||
# state detection check if it's enabled in the config
|
||||
if self.check_states:
|
||||
self.check_state(context, "setup_complete")
|
||||
|
||||
def do_post_install(self, context):
|
||||
ApkWorkload.do_post_install(self, context)
|
||||
self._deploy_assets(context, self.assets_push_timeout)
|
||||
@ -462,6 +699,10 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
ReventWorkload.run(self, context)
|
||||
|
||||
def teardown(self, context):
|
||||
# state detection check if it's enabled in the config
|
||||
if self.check_states:
|
||||
self.check_state(context, "run_complete")
|
||||
|
||||
if not self.saved_state_file:
|
||||
ApkWorkload.teardown(self, context)
|
||||
else:
|
||||
@ -493,3 +734,22 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
||||
self.device.busybox,
|
||||
ondevice_cache)
|
||||
self.device.execute(deploy_command, timeout=timeout, as_root=True)
|
||||
|
||||
def _check_statedetection_files(self, context):
|
||||
try:
|
||||
self.statedefs_dir = context.resolver.get(File(self, 'state_definitions'))
|
||||
except ResourceError:
|
||||
self.logger.warning("State definitions directory not found. Disabling state detection.")
|
||||
self.check_states = False # pylint: disable=W0201
|
||||
|
||||
def check_state(self, context, phase):
|
||||
try:
|
||||
self.logger.info("\tChecking workload state...")
|
||||
screenshotPath = os.path.join(context.output_directory, "screen.png")
|
||||
self.device.capture_screen(screenshotPath)
|
||||
stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase)
|
||||
if not stateCheck:
|
||||
raise WorkloadError("Unexpected state after setup")
|
||||
except state_detector.StateDefinitionError as e:
|
||||
msg = "State definitions or template files missing or invalid ({}). Skipping state detection."
|
||||
self.logger.warning(msg.format(e.message))
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/bin/arm64/sqlite3
Normal file
BIN
wlauto/common/bin/arm64/sqlite3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/bin/armeabi/sqlite3
Normal file
BIN
wlauto/common/bin/armeabi/sqlite3
Normal file
Binary file not shown.
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -462,7 +462,7 @@ class BaseGem5Device(object):
|
||||
# gem5 might be slow. Hence, we need to make the ping timeout very long.
|
||||
def ping(self):
|
||||
self.logger.debug("Pinging gem5 to see if it is still alive")
|
||||
self.gem5_shell('ls /', timeout=self.longdelay)
|
||||
self.gem5_shell('ls /', timeout=self.long_delay)
|
||||
|
||||
# Additional Android-specific methods.
|
||||
def forward_port(self, _): # pylint: disable=R0201
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import base64
|
||||
import socket
|
||||
from collections import namedtuple
|
||||
from subprocess import CalledProcessError
|
||||
@ -39,6 +40,8 @@ FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'opti
|
||||
PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
|
||||
LsmodEntry = namedtuple('LsmodEntry', ['name', 'size', 'use_count', 'used_by'])
|
||||
|
||||
GOOGLE_DNS_SERVER_ADDRESS = '8.8.8.8'
|
||||
|
||||
|
||||
class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
@ -92,12 +95,18 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
'''),
|
||||
Parameter('binaries_directory',
|
||||
description='Location of executable binaries on this device (must be in PATH).'),
|
||||
Parameter('working_directory',
|
||||
description='''
|
||||
Working directory to be used by WA. This must be in a location where the specified user
|
||||
has write permissions. This will default to /home/<username>/wa (or to /root/wa, if
|
||||
username is 'root').
|
||||
'''),
|
||||
|
||||
]
|
||||
|
||||
runtime_parameters = [
|
||||
RuntimeParameter('sysfile_values', 'get_sysfile_values', 'set_sysfile_values', value_name='params'),
|
||||
CoreParameter('${core}_cores', 'get_number_of_online_cpus', 'set_number_of_online_cpus',
|
||||
CoreParameter('${core}_cores', 'get_number_of_online_cores', 'set_number_of_online_cores',
|
||||
value_name='number'),
|
||||
CoreParameter('${core}_min_frequency', 'get_core_min_frequency', 'set_core_min_frequency',
|
||||
value_name='freq'),
|
||||
@ -128,6 +137,10 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
self._abi = val
|
||||
return self._abi
|
||||
|
||||
@property
|
||||
def supported_abi(self):
|
||||
return [self.abi]
|
||||
|
||||
@property
|
||||
def online_cpus(self):
|
||||
val = self.get_sysfile_value('/sys/devices/system/cpu/online')
|
||||
@ -214,7 +227,10 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
outfile = os.path.join(context.host_working_directory, normname)
|
||||
if self.is_file(propfile):
|
||||
with open(outfile, 'w') as wfh:
|
||||
wfh.write(self.execute('cat {}'.format(propfile)))
|
||||
if propfile.endswith(".gz"):
|
||||
wfh.write(self.execute('{} zcat {}'.format(self.busybox, propfile)))
|
||||
else:
|
||||
wfh.write(self.execute('cat {}'.format(propfile)))
|
||||
elif self.is_directory(propfile):
|
||||
self.pull_file(propfile, outfile)
|
||||
else:
|
||||
@ -226,7 +242,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
self.logger.debug('Could not pull property file "{}"'.format(propfile))
|
||||
return {}
|
||||
|
||||
def get_sysfile_value(self, sysfile, kind=None):
|
||||
def get_sysfile_value(self, sysfile, kind=None, binary=False):
|
||||
"""
|
||||
Get the contents of the specified sysfile.
|
||||
|
||||
@ -236,28 +252,49 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
be any Python callable that takes a single str argument.
|
||||
If not specified or is None, the contents will be returned
|
||||
as a string.
|
||||
:param binary: Whether the value should be encoded into base64 for reading
|
||||
to deal with binary format.
|
||||
|
||||
"""
|
||||
output = self.execute('cat \'{}\''.format(sysfile), as_root=self.is_rooted).strip() # pylint: disable=E1103
|
||||
if binary:
|
||||
output = self.execute('{} base64 {}'.format(self.busybox, sysfile), as_root=self.is_rooted).strip()
|
||||
output = output.decode('base64')
|
||||
else:
|
||||
output = self.execute('cat \'{}\''.format(sysfile), as_root=self.is_rooted).strip() # pylint: disable=E1103
|
||||
if kind:
|
||||
return kind(output)
|
||||
else:
|
||||
return output
|
||||
|
||||
def set_sysfile_value(self, sysfile, value, verify=True):
|
||||
def set_sysfile_value(self, sysfile, value, verify=True, binary=False):
|
||||
"""
|
||||
Set the value of the specified sysfile. By default, the value will be checked afterwards.
|
||||
Can be overridden by setting ``verify`` parameter to ``False``.
|
||||
Can be overridden by setting ``verify`` parameter to ``False``. By default binary values
|
||||
will not be written correctly this can be changed by setting the ``binary`` parameter to
|
||||
``True``.
|
||||
|
||||
"""
|
||||
value = str(value)
|
||||
self.execute('echo {} > \'{}\''.format(value, sysfile), check_exit_code=False, as_root=True)
|
||||
if binary:
|
||||
# Value is already string encoded, so need to decode before encoding in base64
|
||||
try:
|
||||
value = str(value.decode('string_escape'))
|
||||
except ValueError as e:
|
||||
msg = 'Can not interpret value "{}" for "{}": {}'
|
||||
raise ValueError(msg.format(value, sysfile, e.message))
|
||||
|
||||
encoded_value = base64.b64encode(value)
|
||||
cmd = 'echo {} | {} base64 -d > \'{}\''.format(encoded_value, self.busybox, sysfile)
|
||||
else:
|
||||
cmd = 'echo {} > \'{}\''.format(value, sysfile)
|
||||
self.execute(cmd, check_exit_code=False, as_root=True)
|
||||
|
||||
if verify:
|
||||
output = self.get_sysfile_value(sysfile)
|
||||
output = self.get_sysfile_value(sysfile, binary=binary)
|
||||
if output.strip() != value: # pylint: disable=E1103
|
||||
message = 'Could not set the value of {} to {}'.format(sysfile, value)
|
||||
raise DeviceError(message)
|
||||
self._written_sysfiles.append(sysfile)
|
||||
self._written_sysfiles.append((sysfile, binary))
|
||||
|
||||
def get_sysfile_values(self):
|
||||
"""
|
||||
@ -266,21 +303,24 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
"""
|
||||
values = {}
|
||||
for sysfile in self._written_sysfiles:
|
||||
values[sysfile] = self.get_sysfile_value(sysfile)
|
||||
for sysfile, binary in self._written_sysfiles:
|
||||
values[sysfile] = self.get_sysfile_value(sysfile, binary=binary)
|
||||
return values
|
||||
|
||||
def set_sysfile_values(self, params):
|
||||
"""
|
||||
The plural version of ``set_sysfile_value``. Takes a single parameter which is a mapping of
|
||||
file paths to values to be set. By default, every value written will be verified. The can
|
||||
be disabled for individual paths by appending ``'!'`` to them.
|
||||
file paths to values to be set. By default, every value written will be verified. This can
|
||||
be disabled for individual paths by appending ``'!'`` to them. To enable values being
|
||||
written as binary data, a ``'^'`` can be prefixed to the path.
|
||||
|
||||
"""
|
||||
for sysfile, value in params.iteritems():
|
||||
verify = not sysfile.endswith('!')
|
||||
sysfile = sysfile.rstrip('!')
|
||||
self.set_sysfile_value(sysfile, value, verify=verify)
|
||||
binary = sysfile.startswith('^')
|
||||
sysfile = sysfile.lstrip('^')
|
||||
self.set_sysfile_value(sysfile, value, verify=verify, binary=binary)
|
||||
|
||||
def deploy_busybox(self, context, force=False):
|
||||
"""
|
||||
@ -309,6 +349,29 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
please see: https://pythonhosted.org/wlauto/writing_extensions.html""")
|
||||
|
||||
def is_network_connected(self):
|
||||
"""
|
||||
Checks for internet connectivity on the device by pinging IP address provided.
|
||||
|
||||
:param ip_address: IP address to ping. Default is Google's public DNS server (8.8.8.8)
|
||||
|
||||
:returns: ``True`` if internet is available, ``False`` otherwise.
|
||||
|
||||
"""
|
||||
self.logger.debug('Checking for internet connectivity...')
|
||||
return self._ping_server(GOOGLE_DNS_SERVER_ADDRESS)
|
||||
|
||||
def _ping_server(self, ip_address, timeout=1, packet_count=1):
|
||||
output = self.execute('ping -q -c {} -w {} {}'.format(packet_count, timeout, ip_address),
|
||||
check_exit_code=False)
|
||||
|
||||
if 'network is unreachable' in output.lower():
|
||||
self.logger.debug('Cannot find IP address {}'.format(ip_address))
|
||||
return False
|
||||
else:
|
||||
self.logger.debug('Found IP address {}'.format(ip_address))
|
||||
return True
|
||||
|
||||
def get_binary_path(self, name, search_system_binaries=True):
|
||||
"""
|
||||
Searches the devices ``binary_directory`` for the given binary,
|
||||
@ -390,7 +453,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
signal_string = '-s {}'.format(signal) if signal else ''
|
||||
self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
|
||||
|
||||
def killall(self, process_name, signal=None, as_root=False): # pylint: disable=W0221
|
||||
def killall(self, process_name, signal=None, as_root=None): # pylint: disable=W0221
|
||||
"""
|
||||
Kill all processes with the specified name.
|
||||
|
||||
@ -401,6 +464,8 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
Modified in version 2.1.5: added ``as_root`` parameter.
|
||||
|
||||
"""
|
||||
if as_root is None:
|
||||
as_root = self.is_rooted
|
||||
for pid in self.get_pids_of(process_name):
|
||||
self.kill(pid, signal=signal, as_root=as_root)
|
||||
|
||||
@ -412,20 +477,6 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
else:
|
||||
raise ValueError(c)
|
||||
|
||||
def get_number_of_online_cpus(self, c):
|
||||
return len(self.get_online_cpus(c))
|
||||
|
||||
def set_number_of_online_cpus(self, core, number):
|
||||
core_ids = [i for i, c in enumerate(self.core_names) if c == core]
|
||||
max_cores = len(core_ids)
|
||||
if number > max_cores:
|
||||
message = 'Attempting to set the number of active {} to {}; maximum is {}'
|
||||
raise ValueError(message.format(core, number, max_cores))
|
||||
for i in xrange(0, number):
|
||||
self.enable_cpu(core_ids[i])
|
||||
for i in xrange(number, max_cores):
|
||||
self.disable_cpu(core_ids[i])
|
||||
|
||||
# hotplug
|
||||
|
||||
def enable_cpu(self, cpu):
|
||||
@ -464,17 +515,17 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
sysfile = '/sys/devices/system/cpu/{}/online'.format(cpu)
|
||||
self.set_sysfile_value(sysfile, status)
|
||||
|
||||
def get_number_of_active_cores(self, core):
|
||||
def get_number_of_online_cores(self, core):
|
||||
if core not in self.core_names:
|
||||
raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(self.core_names))))
|
||||
active_cpus = self.active_cpus
|
||||
online_cpus = self.online_cpus
|
||||
num_active_cores = 0
|
||||
for i, c in enumerate(self.core_names):
|
||||
if c == core and i in active_cpus:
|
||||
if c == core and i in online_cpus:
|
||||
num_active_cores += 1
|
||||
return num_active_cores
|
||||
|
||||
def set_number_of_active_cores(self, core, number): # NOQA
|
||||
def set_number_of_online_cores(self, core, number): # NOQA
|
||||
if core not in self.core_names:
|
||||
raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(self.core_names))))
|
||||
core_ids = [i for i, c in enumerate(self.core_names) if c == core]
|
||||
@ -487,8 +538,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
# make sure at least one other core is enabled to avoid trying to
|
||||
# hotplug everything.
|
||||
for i, c in enumerate(self.core_names):
|
||||
if c != core:
|
||||
self.enable_cpu(i)
|
||||
if c != core and i in self.online_cpus:
|
||||
break
|
||||
else: # did not find one
|
||||
raise ValueError('Cannot hotplug all cpus on the device!')
|
||||
@ -540,7 +590,8 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
def get_device_model(self):
|
||||
if self.file_exists("/proc/device-tree/model"):
|
||||
raw_model = self.execute("cat /proc/device-tree/model")
|
||||
return '_'.join(raw_model.split()[:2])
|
||||
device_model_to_return = '_'.join(raw_model.split()[:2])
|
||||
return device_model_to_return.rstrip(' \t\r\n\0')
|
||||
# Right now we don't know any other way to get device model
|
||||
# info in linux on arm platforms
|
||||
return None
|
||||
@ -549,7 +600,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||
|
||||
def _check_ready(self):
|
||||
if not self._is_ready:
|
||||
raise AttributeError('Device not ready.')
|
||||
raise RuntimeError('Device not ready (has connect() been called?)')
|
||||
|
||||
def _get_core_cluster(self, core):
|
||||
"""Returns the first cluster that has cores of the specified type. Raises
|
||||
@ -583,17 +634,11 @@ class LinuxDevice(BaseLinuxDevice):
|
||||
description='Optionally, telnet may be used instead of ssh, though this is discouraged.'),
|
||||
Parameter('boot_timeout', kind=int, default=120,
|
||||
description='How long to try to connect to the device after a reboot.'),
|
||||
|
||||
Parameter('working_directory', default=None,
|
||||
description='''
|
||||
Working directory to be used by WA. This must be in a location where the specified user
|
||||
has write permissions. This will default to /home/<username>/wa (or to /root/wa, if
|
||||
username is 'root').
|
||||
'''),
|
||||
]
|
||||
|
||||
@property
|
||||
def is_rooted(self):
|
||||
self._check_ready()
|
||||
if self._is_rooted is None:
|
||||
# First check if the user is root
|
||||
try:
|
||||
@ -632,7 +677,11 @@ class LinuxDevice(BaseLinuxDevice):
|
||||
# Power control
|
||||
|
||||
def reset(self):
|
||||
self.execute('reboot', as_root=True)
|
||||
try:
|
||||
self.execute('reboot', as_root=True)
|
||||
except DeviceError as e:
|
||||
if 'Connection dropped' not in e.message:
|
||||
raise e
|
||||
self._is_ready = False
|
||||
|
||||
def hard_reset(self):
|
||||
@ -644,8 +693,15 @@ class LinuxDevice(BaseLinuxDevice):
|
||||
else:
|
||||
self.reset()
|
||||
self.logger.debug('Waiting for device...')
|
||||
# Wait a fixed delay before starting polling to give the device time to
|
||||
# shut down, otherwise, might create the connection while it's still shutting
|
||||
# down resulting in subsequenct connection failing.
|
||||
initial_delay = 20
|
||||
time.sleep(initial_delay)
|
||||
boot_timeout = max(self.boot_timeout - initial_delay, 10)
|
||||
|
||||
start_time = time.time()
|
||||
while (time.time() - start_time) < self.boot_timeout:
|
||||
while (time.time() - start_time) < boot_timeout:
|
||||
try:
|
||||
s = socket.create_connection((self.host, self.port), timeout=5)
|
||||
s.close()
|
||||
@ -669,15 +725,6 @@ class LinuxDevice(BaseLinuxDevice):
|
||||
|
||||
# Execution
|
||||
|
||||
def has_root(self):
|
||||
try:
|
||||
self.execute('ls /', as_root=True)
|
||||
return True
|
||||
except DeviceError as e:
|
||||
if 'not in the sudoers file' not in e.message:
|
||||
raise e
|
||||
return False
|
||||
|
||||
def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False,
|
||||
as_root=False, strip_colors=True, **kwargs):
|
||||
"""
|
||||
@ -720,13 +767,15 @@ class LinuxDevice(BaseLinuxDevice):
|
||||
except CalledProcessError as e:
|
||||
raise DeviceError(e)
|
||||
|
||||
def kick_off(self, command, as_root=False):
|
||||
def kick_off(self, command, as_root=None):
|
||||
"""
|
||||
Like execute but closes adb session and returns immediately, leaving the command running on the
|
||||
device (this is different from execute(background=True) which keeps adb connection open and returns
|
||||
Like execute but closes ssh session and returns immediately, leaving the command running on the
|
||||
device (this is different from execute(background=True) which keeps ssh connection open and returns
|
||||
a subprocess object).
|
||||
|
||||
"""
|
||||
if as_root is None:
|
||||
as_root = self.is_rooted
|
||||
self._check_ready()
|
||||
command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
|
||||
return self.shell.execute(command, as_root=as_root)
|
||||
|
165
wlauto/common/linux/workload.py
Normal file
165
wlauto/common/linux/workload.py
Normal file
@ -0,0 +1,165 @@
|
||||
# Copyright 2017 ARM Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import os
|
||||
from math import ceil
|
||||
|
||||
from wlauto.core.extension import Parameter
|
||||
from wlauto.core.workload import Workload
|
||||
from wlauto.core.resource import NO_ONE
|
||||
from wlauto.common.resources import Executable
|
||||
from wlauto.common.android.resources import ReventFile
|
||||
from wlauto.exceptions import WorkloadError
|
||||
from wlauto.utils.revent import ReventRecording
|
||||
|
||||
class ReventWorkload(Workload):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
|
||||
description = """
|
||||
A workload for playing back revent recordings. You can supply three
|
||||
different files:
|
||||
|
||||
1. {device_model}.setup.revent
|
||||
2. {device_model}.run.revent
|
||||
3. {device_model}.teardown.revent
|
||||
|
||||
You may generate these files using the wa record command using the -s flag
|
||||
to specify the stage (``setup``, ``run``, ``teardown``)
|
||||
|
||||
You may also supply an 'idle_time' in seconds in place of the run file.
|
||||
The ``run`` file may only be omitted if you choose to run this way, but
|
||||
while running idle may supply ``setup`` and ``teardown`` files.
|
||||
|
||||
To use a ``setup`` or ``teardown`` file set the setup_required and/or
|
||||
teardown_required class attributes to True (default: False).
|
||||
|
||||
N.B. This is the default description. You may overwrite this for your
|
||||
workload to include more specific information.
|
||||
|
||||
"""
|
||||
|
||||
setup_required = False
|
||||
teardown_required = False
|
||||
|
||||
parameters = [
|
||||
Parameter(
|
||||
'idle_time', kind=int, default=None,
|
||||
description='''
|
||||
The time you wish the device to remain idle for (if a value is
|
||||
given then this overrides any .run revent file).
|
||||
'''),
|
||||
]
|
||||
|
||||
def __init__(self, device, _call_super=True, **kwargs):
|
||||
if _call_super:
|
||||
Workload.__init__(self, device, **kwargs)
|
||||
self.setup_timeout = kwargs.get('setup_timeout', None)
|
||||
self.run_timeout = kwargs.get('run_timeout', None)
|
||||
self.teardown_timeout = kwargs.get('teardown_timeout', None)
|
||||
self.revent_setup_file = None
|
||||
self.revent_run_file = None
|
||||
self.revent_teardown_file = None
|
||||
self.on_device_setup_revent = None
|
||||
self.on_device_run_revent = None
|
||||
self.on_device_teardown_revent = None
|
||||
self.statedefs_dir = None
|
||||
|
||||
def initialize(self, context):
|
||||
devpath = self.device.path
|
||||
self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent')
|
||||
|
||||
def setup(self, context):
|
||||
devpath = self.device.path
|
||||
if self.setup_required:
|
||||
self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup'))
|
||||
if self.revent_setup_file:
|
||||
self.on_device_setup_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_setup_file)[-1])
|
||||
duration = ReventRecording(self.revent_setup_file).duration
|
||||
self.default_setup_timeout = ceil(duration) + 30
|
||||
if not self.idle_time:
|
||||
self.revent_run_file = context.resolver.get(ReventFile(self, 'run'))
|
||||
if self.revent_run_file:
|
||||
self.on_device_run_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_run_file)[-1])
|
||||
self.default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30
|
||||
if self.teardown_required:
|
||||
self.revent_teardown_file = context.resolver.get(ReventFile(self, 'teardown'))
|
||||
if self.revent_teardown_file:
|
||||
self.on_device_teardown_revent = devpath.join(self.device.working_directory,
|
||||
os.path.split(self.revent_teardown_file)[-1])
|
||||
duration = ReventRecording(self.revent_teardown_file).duration
|
||||
self.default_teardown_timeout = ceil(duration) + 30
|
||||
self._check_revent_files(context)
|
||||
|
||||
Workload.setup(self, context)
|
||||
|
||||
if self.revent_setup_file is not None:
|
||||
self.setup_timeout = self.setup_timeout or self.default_setup_timeout
|
||||
self.device.killall('revent')
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent)
|
||||
self.device.execute(command, timeout=self.setup_timeout)
|
||||
self.logger.debug('Revent setup completed.')
|
||||
|
||||
def run(self, context):
|
||||
if not self.idle_time:
|
||||
self.run_timeout = self.run_timeout or self.default_run_timeout
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent)
|
||||
self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent)))
|
||||
self.device.execute(command, timeout=self.run_timeout)
|
||||
self.logger.debug('Replay completed.')
|
||||
else:
|
||||
self.logger.info('Idling for ' + str(self.idle_time) + ' seconds.')
|
||||
self.device.sleep(self.idle_time)
|
||||
self.logger.info('Successfully did nothing for ' + str(self.idle_time) + ' seconds!')
|
||||
|
||||
def update_result(self, context):
|
||||
pass
|
||||
|
||||
def teardown(self, context):
|
||||
if self.revent_teardown_file is not None:
|
||||
self.teardown_timeout = self.teardown_timeout or self.default_teardown_timeout
|
||||
command = '{} replay {}'.format(self.on_device_revent_binary,
|
||||
self.on_device_teardown_revent)
|
||||
self.device.execute(command, timeout=self.teardown_timeout)
|
||||
self.logger.debug('Replay completed.')
|
||||
self.device.killall('revent')
|
||||
if self.revent_setup_file is not None:
|
||||
self.device.delete_file(self.on_device_setup_revent)
|
||||
if not self.idle_time:
|
||||
self.device.delete_file(self.on_device_run_revent)
|
||||
if self.revent_teardown_file is not None:
|
||||
self.device.delete_file(self.on_device_teardown_revent)
|
||||
|
||||
def _check_revent_files(self, context):
|
||||
# check the revent binary
|
||||
revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
||||
if not os.path.isfile(revent_binary):
|
||||
message = '{} does not exist. '.format(revent_binary)
|
||||
message += 'Please build revent for your system and place it in that location'
|
||||
raise WorkloadError(message)
|
||||
if not self.revent_run_file and not self.idle_time:
|
||||
# pylint: disable=too-few-format-args
|
||||
message = 'It seems a {0}.run.revent file does not exist, '\
|
||||
'Please provide one for your device: {0}.'
|
||||
raise WorkloadError(message.format(self.device.name))
|
||||
|
||||
self.on_device_revent_binary = self.device.install_executable(revent_binary)
|
||||
if self.revent_setup_file is not None:
|
||||
self.device.push_file(self.revent_setup_file, self.on_device_setup_revent)
|
||||
if not self.idle_time:
|
||||
self.device.push_file(self.revent_run_file, self.on_device_run_revent)
|
||||
if self.revent_teardown_file is not None:
|
||||
self.device.push_file(self.revent_teardown_file, self.on_device_teardown_revent)
|
@ -54,11 +54,15 @@ retry_on_status = ['FAILED', 'PARTIAL']
|
||||
# How many times a job will be re-run before giving up
|
||||
max_retries = 3
|
||||
|
||||
# If WA should delete its files from the device after the run is completed
|
||||
clean_up = False
|
||||
|
||||
####################################################################################################
|
||||
######################################### Device Settings ##########################################
|
||||
####################################################################################################
|
||||
# Specify the device you want to run workload automation on. This must be a #
|
||||
# string with the ID of the device. At the moment, only 'TC2' is supported. #
|
||||
# string with the ID of the device. Common options are 'generic_android' and #
|
||||
# 'generic_linux'. Run ``wa list devices`` to see all available options. #
|
||||
# #
|
||||
device = 'generic_android'
|
||||
|
||||
@ -84,7 +88,7 @@ device_config = dict(
|
||||
|
||||
|
||||
####################################################################################################
|
||||
################################### Instrumention Configuration ####################################
|
||||
################################### Instrumentation Configuration ####################################
|
||||
####################################################################################################
|
||||
# This defines the additionnal instrumentation that will be enabled during workload execution, #
|
||||
# which in turn determines what additional data (such as /proc/interrupts content or Streamline #
|
||||
@ -189,7 +193,7 @@ logging = {
|
||||
####################################################################################################
|
||||
#################################### Instruments Configuration #####################################
|
||||
####################################################################################################
|
||||
# Instrumention Configuration is related to specific insturment's settings. Some of the #
|
||||
# Instrumentation Configuration is related to specific instrument's settings. Some of the #
|
||||
# instrumentations require specific settings in order for them to work. These settings are #
|
||||
# specified here. #
|
||||
# Note that these settings only take effect if the corresponding instrument is
|
||||
@ -222,10 +226,10 @@ logging = {
|
||||
####################################################################################################
|
||||
######################################### DAQ configuration ########################################
|
||||
|
||||
# The host address of the machine that runs the daq Server which the insturment communicates with
|
||||
# The host address of the machine that runs the daq Server which the instrument communicates with
|
||||
#daq_server_host = '10.1.17.56'
|
||||
|
||||
# The port number for daq Server in which daq insturment communicates with
|
||||
# The port number for daq Server in which daq instrument communicates with
|
||||
#daq_server_port = 56788
|
||||
|
||||
# The values of resistors 1 and 2 (in Ohms) across which the voltages are measured
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -114,8 +114,8 @@ class ConfigLoader(object):
|
||||
new_config = load_struct_from_yaml(source)
|
||||
else:
|
||||
raise ConfigError('Unknown config format: {}'.format(source))
|
||||
except LoadSyntaxError as e:
|
||||
raise ConfigError(e)
|
||||
except (LoadSyntaxError, ValueError) as e:
|
||||
raise ConfigError('Invalid config "{}":\n\t{}'.format(source, e))
|
||||
|
||||
self._config = merge_dicts(self._config, new_config,
|
||||
list_duplicates='first',
|
||||
@ -152,7 +152,8 @@ def init_environment(env_root, dep_dir, extension_paths, overwrite_existing=Fals
|
||||
os.makedirs(env_root)
|
||||
with open(os.path.join(_this_dir, '..', 'config_example.py')) as rf:
|
||||
text = re.sub(r'""".*?"""', '', rf.read(), 1, re.DOTALL)
|
||||
with open(os.path.join(_env_root, 'config.py'), 'w') as wf:
|
||||
config_path = os.path.join(env_root, 'config.py')
|
||||
with open(config_path, 'w') as wf:
|
||||
wf.write(text)
|
||||
|
||||
os.makedirs(dep_dir)
|
||||
@ -173,9 +174,11 @@ def init_environment(env_root, dep_dir, extension_paths, overwrite_existing=Fals
|
||||
os.chown(os.path.join(root, d), uid, gid)
|
||||
for f in files: # pylint: disable=W0621
|
||||
os.chown(os.path.join(root, f), uid, gid)
|
||||
return config_path
|
||||
|
||||
|
||||
_env_root = os.getenv('WA_USER_DIRECTORY', os.path.join(_user_home, '.workload_automation'))
|
||||
_env_root = os.path.abspath(_env_root)
|
||||
_dep_dir = os.path.join(_env_root, 'dependencies')
|
||||
_extension_paths = [os.path.join(_env_root, ext.default_path) for ext in _extensions]
|
||||
_env_var_paths = os.getenv('WA_EXTENSION_PATHS', '')
|
||||
@ -189,7 +192,8 @@ for filename in ['config.py', 'config.yaml']:
|
||||
_env_configs.append(filepath)
|
||||
|
||||
if not os.path.isdir(_env_root):
|
||||
init_environment(_env_root, _dep_dir, _extension_paths)
|
||||
cfg_path = init_environment(_env_root, _dep_dir, _extension_paths)
|
||||
_env_configs.append(cfg_path)
|
||||
elif not _env_configs:
|
||||
filepath = os.path.join(_env_root, 'config.py')
|
||||
with open(os.path.join(_this_dir, '..', 'config_example.py')) as f:
|
||||
@ -211,4 +215,3 @@ if os.path.isfile(_packages_file):
|
||||
|
||||
for config in _env_configs:
|
||||
settings.update(config)
|
||||
|
||||
|
@ -481,6 +481,7 @@ class RunConfiguration(object):
|
||||
RunConfigurationItem('flashing_config', 'dict', 'replace'),
|
||||
RunConfigurationItem('retry_on_status', 'list', 'replace'),
|
||||
RunConfigurationItem('max_retries', 'scalar', 'replace'),
|
||||
RunConfigurationItem('clean_up', 'scalar', 'replace'),
|
||||
]
|
||||
|
||||
# Configuration specified for each workload spec. "workload_parameters"
|
||||
@ -757,7 +758,7 @@ class RunConfiguration(object):
|
||||
if spec.match_selectors(selectors):
|
||||
instrumentation_config = self._raw_config['instrumentation']
|
||||
for instname in spec.instrumentation:
|
||||
if instname not in instrumentation_config:
|
||||
if instname not in instrumentation_config and not instname.startswith('~'):
|
||||
instrumentation_config.append(instname)
|
||||
self.workload_specs.append(spec)
|
||||
|
||||
|
@ -381,7 +381,20 @@ class Device(Extension):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_sysfile_value(self, filepath, value, verify=True):
|
||||
def sleep(self, seconds):
|
||||
"""Sleep for the specified time on the target device.
|
||||
|
||||
:param seconds: Time in seconds to sleep on the device
|
||||
|
||||
The sleep is executed on the device using self.execute(). We
|
||||
set the timeout for this command to be 10 seconds longer than
|
||||
the sleep itself to make sure the command has time to complete
|
||||
before we timeout.
|
||||
|
||||
"""
|
||||
self.execute("sleep {}".format(seconds), timeout=seconds + 10)
|
||||
|
||||
def set_sysfile_value(self, filepath, value, verify=True, binary=False):
|
||||
"""
|
||||
Write the specified value to the specified file on the device
|
||||
and verify that the value has actually been written.
|
||||
@ -390,13 +403,14 @@ class Device(Extension):
|
||||
:param value: The value to be written to the file. Must be
|
||||
an int or a string convertable to an int.
|
||||
:param verify: Specifies whether the value should be verified, once written.
|
||||
:param binary: Specifies whether the value should be written as binary data.
|
||||
|
||||
Should raise DeviceError if could write value.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_sysfile_value(self, sysfile, kind=None):
|
||||
def get_sysfile_value(self, sysfile, kind=None, binary=False):
|
||||
"""
|
||||
Get the contents of the specified sysfile.
|
||||
|
||||
@ -406,6 +420,8 @@ class Device(Extension):
|
||||
be any Python callable that takes a single str argument.
|
||||
If not specified or is None, the contents will be returned
|
||||
as a string.
|
||||
:param binary: Whether the value should be encoded into base64 for reading
|
||||
to deal with binary format.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@ -426,6 +442,13 @@ class Device(Extension):
|
||||
"""
|
||||
pass
|
||||
|
||||
def is_network_connected(self):
|
||||
"""
|
||||
Checks if the device is connected to the internet
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def __str__(self):
|
||||
return 'Device<{}>'.format(self.name)
|
||||
|
||||
|
@ -14,10 +14,12 @@
|
||||
#
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import warnings
|
||||
|
||||
@ -41,6 +43,9 @@ def load_commands(subparsers):
|
||||
for command in ext_loader.list_commands():
|
||||
settings.commands[command.name] = ext_loader.get_command(command.name, subparsers=subparsers)
|
||||
|
||||
def convert_TERM_into_INT_handler(signal, frame):
|
||||
logger.critical("TERM received, aborting")
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
def main():
|
||||
try:
|
||||
@ -62,6 +67,7 @@ def main():
|
||||
settings.update(args.config)
|
||||
init_logging(settings.verbosity)
|
||||
|
||||
signal.signal(signal.SIGTERM, convert_TERM_into_INT_handler)
|
||||
command = settings.commands[args.command]
|
||||
sys.exit(command.execute(args))
|
||||
|
||||
|
@ -56,7 +56,8 @@ from wlauto.core.extension_loader import ExtensionLoader
|
||||
from wlauto.core.resolver import ResourceResolver
|
||||
from wlauto.core.result import ResultManager, IterationResult, RunResult
|
||||
from wlauto.exceptions import (WAError, ConfigError, TimeoutError, InstrumentError,
|
||||
DeviceError, DeviceNotRespondingError)
|
||||
DeviceError, DeviceNotRespondingError, ResourceError,
|
||||
HostError)
|
||||
from wlauto.utils.misc import ensure_directory_exists as _d, get_traceback, merge_dicts, format_duration
|
||||
|
||||
|
||||
@ -204,6 +205,9 @@ class ExecutionContext(object):
|
||||
def add_metric(self, *args, **kwargs):
|
||||
self.result.add_metric(*args, **kwargs)
|
||||
|
||||
def add_classifiers(self, **kwargs):
|
||||
self.result.classifiers.update(kwargs)
|
||||
|
||||
def add_artifact(self, name, path, kind, *args, **kwargs):
|
||||
if self.current_job is None:
|
||||
self.add_run_artifact(name, path, kind, *args, **kwargs)
|
||||
@ -341,6 +345,11 @@ class Executor(object):
|
||||
runner = self._get_runner(result_manager)
|
||||
runner.init_queue(self.config.workload_specs)
|
||||
runner.run()
|
||||
|
||||
if getattr(self.config, "clean_up", False):
|
||||
self.logger.info('Clearing WA files from device')
|
||||
self.device.delete_file(self.device.binaries_directory)
|
||||
self.device.delete_file(self.device.working_directory)
|
||||
self.execute_postamble()
|
||||
|
||||
def execute_postamble(self):
|
||||
@ -633,7 +642,7 @@ class Runner(object):
|
||||
job.iteration = self.context.current_iteration
|
||||
if job.result.status in self.config.retry_on_status:
|
||||
if job.retry >= self.config.max_retries:
|
||||
self.logger.error('Exceeded maxium number of retries. Abandoning job.')
|
||||
self.logger.error('Exceeded maximum number of retries. Abandoning job.')
|
||||
else:
|
||||
self.logger.info('Job status was {}. Retrying...'.format(job.result.status))
|
||||
retry_job = RunnerJob(job.spec, job.retry + 1)
|
||||
@ -728,6 +737,13 @@ class Runner(object):
|
||||
filepath = os.path.join(settings.output_directory, filename)
|
||||
self.device.capture_screen(filepath)
|
||||
|
||||
def _take_uiautomator_dump(self, filename):
|
||||
if self.context.output_directory:
|
||||
filepath = os.path.join(self.context.output_directory, filename)
|
||||
else:
|
||||
filepath = os.path.join(settings.output_directory, filename)
|
||||
self.device.capture_ui_hierarchy(filepath)
|
||||
|
||||
@contextmanager
|
||||
def _handle_errors(self, action, on_error_status=IterationResult.FAILED):
|
||||
try:
|
||||
@ -741,15 +757,21 @@ class Runner(object):
|
||||
if self.current_job:
|
||||
self.current_job.result.status = on_error_status
|
||||
self.current_job.result.add_event(str(we))
|
||||
try:
|
||||
self._take_screenshot('error.png')
|
||||
except Exception, e: # pylint: disable=W0703
|
||||
# We're already in error state, so the fact that taking a
|
||||
# screenshot failed is not surprising...
|
||||
pass
|
||||
|
||||
# There is no point in taking a screenshot ect if the issue is not
|
||||
# with the device but with the host or a missing resource
|
||||
if not (isinstance(we, ResourceError) or isinstance(we, HostError)):
|
||||
try:
|
||||
self._take_screenshot('error.png')
|
||||
if self.device.platform == 'android':
|
||||
self._take_uiautomator_dump('error.uix')
|
||||
except Exception, e: # pylint: disable=W0703
|
||||
# We're already in error state, so the fact that taking a
|
||||
# screenshot failed is not surprising...
|
||||
pass
|
||||
if action:
|
||||
action = action[0].lower() + action[1:]
|
||||
self.logger.error('Error while {}:\n\t{}'.format(action, we))
|
||||
self.logger.error('Error while {}:\n\t{}'.format(action, str(we).replace("\n", "\n\t")))
|
||||
except Exception, e: # pylint: disable=W0703
|
||||
error_text = '{}("{}")'.format(e.__class__.__name__, e)
|
||||
if self.current_job:
|
||||
|
@ -224,18 +224,11 @@ class Param(object):
|
||||
else:
|
||||
new_value = current_value + [value]
|
||||
setattr(obj, self.name, new_value)
|
||||
|
||||
def validate(self, obj):
|
||||
value = getattr(obj, self.name, None)
|
||||
if value is not None:
|
||||
if self.allowed_values:
|
||||
self._validate_allowed_values(obj, value)
|
||||
if self.constraint:
|
||||
self._validate_constraint(obj, value)
|
||||
else:
|
||||
if self.mandatory:
|
||||
msg = 'No value specified for mandatory parameter {} in {}.'
|
||||
raise ConfigError(msg.format(self.name, obj.name))
|
||||
|
||||
def get_type_name(self):
|
||||
typename = str(self.kind)
|
||||
@ -567,7 +560,9 @@ class Extension(object):
|
||||
if self.name is None:
|
||||
raise ValidationError('Name not set for {}'.format(self._classname))
|
||||
for param in self.parameters:
|
||||
param.validate(self)
|
||||
if param.mandatory and getattr(self, param.name, None) is None:
|
||||
msg = 'No value specified for mandatory parameter {} in {}.'
|
||||
raise ConfigError(msg.format(param.name, self.name))
|
||||
|
||||
def initialize(self, context):
|
||||
pass
|
||||
|
@ -32,4 +32,3 @@ def get_extension_type(ext):
|
||||
if isinstance(ext, cls):
|
||||
return name
|
||||
raise ValueError('Unknown extension type: {}'.format(ext.__class__.__name__))
|
||||
|
||||
|
@ -241,7 +241,7 @@ class ManagedCallback(object):
|
||||
except (KeyboardInterrupt, DeviceNotRespondingError, TimeoutError): # pylint: disable=W0703
|
||||
raise
|
||||
except Exception as e: # pylint: disable=W0703
|
||||
logger.error('Error in insturment {}'.format(self.instrument.name))
|
||||
logger.error('Error in instrument {}'.format(self.instrument.name))
|
||||
global failures_detected # pylint: disable=W0603
|
||||
failures_detected = True
|
||||
if isinstance(e, WAError):
|
||||
@ -396,4 +396,3 @@ class Instrument(Extension):
|
||||
|
||||
def __repr__(self):
|
||||
return 'Instrument({})'.format(self.name)
|
||||
|
||||
|
@ -69,7 +69,11 @@ class ResourceResolver(object):
|
||||
self.logger.debug('\t{}'.format(result))
|
||||
return result
|
||||
if strict:
|
||||
raise ResourceError('{} could not be found'.format(resource))
|
||||
if kwargs:
|
||||
criteria = ', '.join(['{}:{}'.format(k, v) for k, v in kwargs.iteritems()])
|
||||
raise ResourceError('{} ({}) could not be found'.format(resource, criteria))
|
||||
else:
|
||||
raise ResourceError('{} could not be found'.format(resource))
|
||||
self.logger.debug('Resource {} not found.'.format(resource))
|
||||
return None
|
||||
|
||||
|
@ -38,8 +38,8 @@ class GetterPriority(object):
|
||||
"""
|
||||
cached = 20
|
||||
preferred = 10
|
||||
remote = 5
|
||||
environment = 0
|
||||
remote = -4
|
||||
external_package = -5
|
||||
package = -10
|
||||
|
||||
|
@ -327,4 +327,3 @@ class Metric(object):
|
||||
return '<{}>'.format(result)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
@ -186,4 +186,3 @@ def send(signal, sender, *args, **kwargs):
|
||||
|
||||
"""
|
||||
dispatcher.send(signal, sender, *args, **kwargs)
|
||||
|
||||
|
@ -18,7 +18,7 @@ from collections import namedtuple
|
||||
|
||||
VersionTuple = namedtuple('Version', ['major', 'minor', 'revision'])
|
||||
|
||||
version = VersionTuple(2, 4, 0)
|
||||
version = VersionTuple(2, 7, 0)
|
||||
|
||||
|
||||
def get_wa_version():
|
||||
|
@ -37,6 +37,7 @@ class Workload(Extension):
|
||||
supported_devices = []
|
||||
supported_platforms = []
|
||||
summary_metrics = []
|
||||
requires_network = False
|
||||
|
||||
def __init__(self, device, **kwargs):
|
||||
"""
|
||||
@ -69,7 +70,7 @@ class Workload(Extension):
|
||||
"""
|
||||
pass
|
||||
|
||||
def setup(self, context):
|
||||
def setup(self, context): # pylint: disable=unused-argument
|
||||
"""
|
||||
Perform the setup necessary to run the workload, such as copying the necessary files
|
||||
to the device, configuring the environments, etc.
|
||||
@ -78,7 +79,8 @@ class Workload(Extension):
|
||||
the workload.
|
||||
|
||||
"""
|
||||
pass
|
||||
if self.requires_network:
|
||||
self.check_network_connected()
|
||||
|
||||
def run(self, context):
|
||||
"""Execute the workload. This is the method that performs the actual "work" of the"""
|
||||
@ -99,6 +101,10 @@ class Workload(Extension):
|
||||
def finalize(self, context):
|
||||
pass
|
||||
|
||||
def check_network_connected(self):
|
||||
if not self.device.is_network_connected():
|
||||
message = 'Workload "{}" requires internet. Device "{}" does not appear to be connected to the internet.'
|
||||
raise WorkloadError(message.format(self.name, self.device.name))
|
||||
|
||||
def __str__(self):
|
||||
return '<Workload {}>'.format(self.name)
|
||||
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -199,16 +199,6 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
||||
props = self._get_android_properties(context)
|
||||
return props
|
||||
|
||||
def disable_screen_lock(self):
|
||||
"""
|
||||
Attempts to disable he screen lock on the device.
|
||||
|
||||
Overridden here as otherwise we have issues with too many backslashes.
|
||||
"""
|
||||
lockdb = '/data/system/locksettings.db'
|
||||
sqlcommand = "update locksettings set value=\'0\' where name=\'screenlock.disabled\';"
|
||||
self.execute('sqlite3 {} "{}"'.format(lockdb, sqlcommand), as_root=True)
|
||||
|
||||
def capture_screen(self, filepath):
|
||||
if BaseGem5Device.capture_screen(self, filepath):
|
||||
return
|
||||
|
@ -81,9 +81,7 @@ class Juno(BigLittleDevice):
|
||||
'fdt_support': True,
|
||||
}
|
||||
),
|
||||
Parameter('bootargs', default='console=ttyAMA0,115200 earlyprintk=pl011,0x7ff80000 '
|
||||
'verbose debug init=/init root=/dev/sda1 rw ip=dhcp '
|
||||
'rootwait video=DVI-D-1:1920x1080R@60',
|
||||
Parameter('bootargs',
|
||||
description='''Default boot arguments to use when boot_arguments were not.'''),
|
||||
]
|
||||
|
||||
@ -158,7 +156,7 @@ class Juno(BigLittleDevice):
|
||||
target.sendline('ip addr list eth0')
|
||||
time.sleep(1)
|
||||
try:
|
||||
target.expect('inet ([1-9]\d*.\d+.\d+.\d+)', timeout=10)
|
||||
target.expect(r'inet ([1-9]\d*.\d+.\d+.\d+)', timeout=10)
|
||||
self.adb_name = target.match.group(1) + ':5555' # pylint: disable=W0201
|
||||
break
|
||||
except pexpect.TIMEOUT:
|
||||
@ -220,4 +218,3 @@ class Juno(BigLittleDevice):
|
||||
def get_android_id(self):
|
||||
# Android ID currenlty not set properly in Juno Android builds.
|
||||
return 'abad1deadeadbeef'
|
||||
|
||||
|
14
wlauto/devices/android/meizumx6/__init__.py
Normal file
14
wlauto/devices/android/meizumx6/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
from wlauto import AndroidDevice
|
||||
|
||||
|
||||
class MeizuMX6(AndroidDevice):
|
||||
|
||||
name = 'meizumx6'
|
||||
|
||||
@property
|
||||
def is_rooted(self):
|
||||
# "su" executable on a rooted Meizu MX6 is targeted
|
||||
# specifically towards Android application and cannot
|
||||
# be used to execute a command line shell. This makes it
|
||||
# "broken" from WA prespective.
|
||||
return False
|
@ -35,4 +35,3 @@ class OdroidXU3(AndroidDevice):
|
||||
description='Serial port on which the device is connected'),
|
||||
Parameter('baudrate', default=115200, kind=int, description='Serial connection baud rate'),
|
||||
]
|
||||
|
||||
|
@ -847,4 +847,3 @@ def _slow_sendline(target, line):
|
||||
target.send(c)
|
||||
time.sleep(0.1)
|
||||
target.sendline('')
|
||||
|
||||
|
@ -33,4 +33,3 @@ class Xe503c12Chormebook(LinuxDevice):
|
||||
]
|
||||
|
||||
abi = 'armeabi'
|
||||
|
||||
|
@ -12,5 +12,3 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
|
@ -97,4 +97,3 @@ class ChromeOsDevice(LinuxDevice):
|
||||
else:
|
||||
pass
|
||||
self.ui_status = None
|
||||
|
||||
|
@ -32,4 +32,3 @@ class OdroidXU3LinuxDevice(LinuxDevice):
|
||||
]
|
||||
|
||||
abi = 'armeabi'
|
||||
|
||||
|
@ -55,4 +55,3 @@ logObserver.start()
|
||||
|
||||
def start_logging(level, fmt='%(asctime)s %(levelname)-8s: %(message)s'):
|
||||
logging.basicConfig(level=getattr(logging, level), format=fmt)
|
||||
|
||||
|
7
wlauto/external/revent/Makefile
vendored
7
wlauto/external/revent/Makefile
vendored
@ -1,7 +1,12 @@
|
||||
# CROSS_COMPILE=aarch64-linux-gnu- make
|
||||
#
|
||||
CC=gcc
|
||||
CFLAGS=-static -lc
|
||||
|
||||
ifdef DEBUG
|
||||
CFLAGS=-static -lc -g
|
||||
else
|
||||
CFLAGS=-static -lc -O2
|
||||
endif
|
||||
|
||||
revent: revent.c
|
||||
$(CROSS_COMPILE)$(CC) $(CFLAGS) revent.c -o revent
|
||||
|
1913
wlauto/external/revent/revent.c
vendored
1913
wlauto/external/revent/revent.c
vendored
File diff suppressed because it is too large
Load Diff
11
wlauto/external/sqlite/README
vendored
Normal file
11
wlauto/external/sqlite/README
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
For WA we use a slightly modified version of sqlite3 so that it can
|
||||
be built statically. We used the amalgamated sqlite3 version 3.12.2.
|
||||
which is under the public domain.
|
||||
|
||||
https://www.sqlite.org/download.html
|
||||
|
||||
Build command:
|
||||
gcc shell.c sqlite3.c -lpthread -ldl -static -O2 -fPIC -DPIC -DSQLITE_OMIT_LOAD_EXTENSION
|
||||
|
||||
You will need to apply the diff in static.patch
|
||||
|
20
wlauto/external/sqlite/static.patch
vendored
Normal file
20
wlauto/external/sqlite/static.patch
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
--- shell.c 2016-05-09 15:35:26.952309563 +0100
|
||||
+++ shell.c.bak 2016-05-09 15:33:41.991259588 +0100
|
||||
@@ -4503,7 +4503,7 @@
|
||||
static char *home_dir = NULL;
|
||||
if( home_dir ) return home_dir;
|
||||
|
||||
+/*#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \
|
||||
-#if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \
|
||||
&& !defined(__RTP__) && !defined(_WRS_KERNEL)
|
||||
{
|
||||
struct passwd *pwent;
|
||||
@@ -4512,7 +4512,7 @@
|
||||
home_dir = pwent->pw_dir;
|
||||
}
|
||||
}
|
||||
+#endif*/
|
||||
-#endif
|
||||
|
||||
#if defined(_WIN32_WCE)
|
||||
/* Windows CE (arm-wince-mingw32ce-gcc) does not provide getenv()
|
18
wlauto/external/uiauto/app/build.gradle
vendored
Normal file
18
wlauto/external/uiauto/app/build.gradle
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '25.0.3'
|
||||
defaultConfig {
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 25
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
compile 'com.android.support.test:runner:0.5'
|
||||
compile 'com.android.support.test:rules:0.5'
|
||||
compile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||
}
|
9
wlauto/external/uiauto/app/src/main/AndroidManifest.xml
vendored
Normal file
9
wlauto/external/uiauto/app/src/main/AndroidManifest.xml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.arm.wlauto.uiauto">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
</application>
|
||||
</manifest>
|
58
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/ApplaunchInterface.java
vendored
Normal file
58
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/ApplaunchInterface.java
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
|
||||
/* Copyright 2013-2016 ARM Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.arm.wlauto.uiauto;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.test.uiautomator.UiObject;
|
||||
|
||||
// Import the uiautomator libraries
|
||||
|
||||
|
||||
/**
|
||||
* ApplaunchInterface.java
|
||||
* Interface used for enabling uxperfapplaunch workload.
|
||||
* This interface gets implemented by all workloads that support application launch
|
||||
* instrumentation.
|
||||
*/
|
||||
|
||||
public interface ApplaunchInterface {
|
||||
|
||||
/**
|
||||
* Sets the launchEndObject of a workload, which is a UiObject that marks
|
||||
* the end of the application launch.
|
||||
*/
|
||||
public UiObject getLaunchEndObject();
|
||||
|
||||
/**
|
||||
* Runs the Uiautomation methods for clearing the initial run
|
||||
* dialogues on the first time installation of an application package.
|
||||
*/
|
||||
public void runApplicationInitialization() throws Exception;
|
||||
|
||||
/**
|
||||
* Provides the application launch command of the application which is
|
||||
* constructed as a string from the workload.
|
||||
*/
|
||||
public String getLaunchCommand();
|
||||
|
||||
/** Passes the workload parameters. */
|
||||
public void setWorkloadParameters(Bundle parameters);
|
||||
|
||||
/** Initialize the instrumentation for the workload */
|
||||
public void initialize_instrumentation();
|
||||
|
||||
}
|
728
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/BaseUiAutomation.java
vendored
Normal file
728
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/BaseUiAutomation.java
vendored
Normal file
@ -0,0 +1,728 @@
|
||||
/* Copyright 2013-2016 ARM Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.arm.wlauto.uiauto;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.support.test.uiautomator.UiObject;
|
||||
import android.support.test.uiautomator.UiSelector;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||
import android.support.test.uiautomator.UiScrollable;
|
||||
import android.support.test.uiautomator.UiWatcher;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getArguments;
|
||||
|
||||
public class BaseUiAutomation {
|
||||
|
||||
// Time in milliseconds
|
||||
public long uiAutoTimeout = 4000;
|
||||
|
||||
public enum ScreenOrientation { RIGHT, NATURAL, LEFT };
|
||||
public enum Direction { UP, DOWN, LEFT, RIGHT, NULL };
|
||||
public enum PinchType { IN, OUT, NULL };
|
||||
|
||||
public static final int CLICK_REPEAT_INTERVAL_MINIMUM = 5;
|
||||
public static final int CLICK_REPEAT_INTERVAL_DEFAULT = 50;
|
||||
|
||||
public Bundle parameters;
|
||||
|
||||
public Instrumentation mInstrumentation;
|
||||
public Context mContext;
|
||||
public UiDevice mDevice;
|
||||
|
||||
public void initialize_instrumentation(){
|
||||
mInstrumentation = InstrumentationRegistry.getInstrumentation();
|
||||
mDevice = UiDevice.getInstance(mInstrumentation);
|
||||
mContext = mInstrumentation.getTargetContext();
|
||||
}
|
||||
|
||||
/*
|
||||
* Used by clickUiObject() methods in order to provide a consistent API
|
||||
*/
|
||||
public enum FindByCriteria { BY_ID, BY_TEXT, BY_DESC; }
|
||||
|
||||
/**
|
||||
* Basic marker API for workloads to generate start and end markers for
|
||||
* deliminating and timing actions. Markers are output to logcat with debug
|
||||
* priority. Actions represent a series of UI interactions to time.
|
||||
*
|
||||
* The marker API provides a way for instruments and result processors to hook into
|
||||
* per-action timings by parsing logcat logs produced per workload iteration.
|
||||
*
|
||||
* The marker output consists of a logcat tag 'UX_PERF' and a message. The
|
||||
* message consists of a name for the action and a timestamp. The timestamp
|
||||
* is separated by a single space from the name of the action.
|
||||
*
|
||||
* Typical usage:
|
||||
*
|
||||
* ActionLogger logger = ActionLogger("testTag", parameters);
|
||||
* logger.start();
|
||||
* // actions to be recorded
|
||||
* logger.stop();
|
||||
*/
|
||||
public class ActionLogger {
|
||||
|
||||
private String testTag;
|
||||
private boolean enabled;
|
||||
|
||||
public ActionLogger(String testTag, Bundle parameters) {
|
||||
this.testTag = testTag;
|
||||
this.enabled = parameters.getBoolean("markers_enabled");
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (enabled) {
|
||||
Log.d("UX_PERF", testTag + "_start " + System.nanoTime());
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws Exception {
|
||||
if (enabled) {
|
||||
Log.d("UX_PERF", testTag + "_end " + System.nanoTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sleep(int second) {
|
||||
SystemClock.sleep(second * 1000);
|
||||
}
|
||||
|
||||
public boolean takeScreenshot(String name) {
|
||||
Bundle params = getParams();
|
||||
String pngDir = params.getString("workdir");
|
||||
|
||||
try {
|
||||
return mDevice.takeScreenshot(new File(pngDir, name + ".png"));
|
||||
} catch (NoSuchMethodError e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitText(String text) throws UiObjectNotFoundException {
|
||||
waitText(text, 600);
|
||||
}
|
||||
|
||||
public void waitText(String text, int second) throws UiObjectNotFoundException {
|
||||
UiSelector selector = new UiSelector();
|
||||
UiObject textObj = mDevice.findObject(selector.text(text)
|
||||
.className("android.widget.TextView"));
|
||||
waitObject(textObj, second);
|
||||
}
|
||||
|
||||
public void waitObject(UiObject obj) throws UiObjectNotFoundException {
|
||||
waitObject(obj, 600);
|
||||
}
|
||||
|
||||
public void waitObject(UiObject obj, int second) throws UiObjectNotFoundException {
|
||||
if (!obj.waitForExists(second * 1000)) {
|
||||
throw new UiObjectNotFoundException("UiObject is not found: "
|
||||
+ obj.getSelector().toString());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean waitUntilNoObject(UiObject obj, int second) {
|
||||
return obj.waitUntilGone(second * 1000);
|
||||
}
|
||||
|
||||
public void clearLogcat() throws Exception {
|
||||
Runtime.getRuntime().exec("logcat -c");
|
||||
}
|
||||
|
||||
public void waitForLogcatText(String searchText, long timeout) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Process process = Runtime.getRuntime().exec("logcat");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
boolean found = false;
|
||||
while ((currentTime - startTime) < timeout) {
|
||||
sleep(2); // poll every two seconds
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.contains(searchText)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
currentTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
process.destroy();
|
||||
|
||||
if ((currentTime - startTime) >= timeout) {
|
||||
throw new TimeoutException(String.format("Timed out waiting for Logcat text \"%s\"",
|
||||
searchText));
|
||||
}
|
||||
}
|
||||
|
||||
public Integer[] splitVersion(String versionString) {
|
||||
String pattern = "(\\d+).(\\d+).(\\d+)";
|
||||
Pattern r = Pattern.compile(pattern);
|
||||
ArrayList<Integer> result = new ArrayList<Integer>();
|
||||
|
||||
Matcher m = r.matcher(versionString);
|
||||
if (m.find() && m.groupCount() > 0) {
|
||||
for(int i=1; i<=m.groupCount(); i++) {
|
||||
result.add(Integer.parseInt(m.group(i)));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(versionString + " - unknown format");
|
||||
}
|
||||
return result.toArray(new Integer[result.size()]);
|
||||
}
|
||||
|
||||
//Return values:
|
||||
// -1 = a lower than b
|
||||
// 0 = a and b equal
|
||||
// 1 = a greater than b
|
||||
public int compareVersions(Integer[] a, Integer[] b) {
|
||||
if (a.length != b.length) {
|
||||
String msg = "Versions do not match format:\n %1$s\n %1$s";
|
||||
msg = String.format(msg, Arrays.toString(a), Arrays.toString(b));
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
for(int i=0; i<a.length; i++) {
|
||||
if(a[i] > b[i])
|
||||
return 1;
|
||||
else if(a[i] < b[i])
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void registerWatcher(String name, UiWatcher watcher) {
|
||||
mDevice.registerWatcher(name, watcher);
|
||||
}
|
||||
|
||||
public void runWatchers() {
|
||||
mDevice.runWatchers();
|
||||
}
|
||||
|
||||
public void removeWatcher(String name) {
|
||||
mDevice.removeWatcher(name);
|
||||
}
|
||||
|
||||
public void pressEnter() {
|
||||
mDevice.pressEnter();
|
||||
}
|
||||
|
||||
public void pressHome() {
|
||||
mDevice.pressHome();
|
||||
}
|
||||
|
||||
public void pressBack() {
|
||||
mDevice.pressBack();
|
||||
}
|
||||
|
||||
public void pressDPadUp() {
|
||||
mDevice.pressDPadUp();
|
||||
}
|
||||
|
||||
public void pressDPadDown() {
|
||||
mDevice.pressDPadDown();
|
||||
}
|
||||
|
||||
public void pressDPadLeft() {
|
||||
mDevice.pressDPadLeft();
|
||||
}
|
||||
|
||||
public void pressDPadRight() {
|
||||
mDevice.pressDPadRight();
|
||||
}
|
||||
|
||||
public int getDisplayHeight() {
|
||||
return mDevice.getDisplayHeight();
|
||||
}
|
||||
|
||||
public int getDisplayWidth() {
|
||||
return mDevice.getDisplayWidth();
|
||||
}
|
||||
|
||||
public int getDisplayCentreWidth() {
|
||||
return getDisplayWidth() / 2;
|
||||
}
|
||||
|
||||
public int getDisplayCentreHeight() {
|
||||
return getDisplayHeight() / 2;
|
||||
}
|
||||
|
||||
public void tapDisplayCentre() {
|
||||
tapDisplay(getDisplayCentreWidth(), getDisplayCentreHeight());
|
||||
}
|
||||
|
||||
public void tapDisplay(int x, int y) {
|
||||
mDevice.click(x, y);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeUp(int steps) {
|
||||
mDevice.swipe(
|
||||
getDisplayCentreWidth(),
|
||||
(getDisplayCentreHeight() + (getDisplayCentreHeight() / 2)),
|
||||
getDisplayCentreWidth(),
|
||||
(getDisplayCentreHeight() / 2),
|
||||
steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeDown(int steps) {
|
||||
mDevice.swipe(
|
||||
getDisplayCentreWidth(),
|
||||
(getDisplayCentreHeight() / 2),
|
||||
getDisplayCentreWidth(),
|
||||
(getDisplayCentreHeight() + (getDisplayCentreHeight() / 2)),
|
||||
steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeLeft(int steps) {
|
||||
mDevice.swipe(
|
||||
(getDisplayCentreWidth() + (getDisplayCentreWidth() / 2)),
|
||||
getDisplayCentreHeight(),
|
||||
(getDisplayCentreWidth() / 2),
|
||||
getDisplayCentreHeight(),
|
||||
steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeRight(int steps) {
|
||||
mDevice.swipe(
|
||||
(getDisplayCentreWidth() / 2),
|
||||
getDisplayCentreHeight(),
|
||||
(getDisplayCentreWidth() + (getDisplayCentreWidth() / 2)),
|
||||
getDisplayCentreHeight(),
|
||||
steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipe(Direction direction, int steps) throws Exception {
|
||||
switch (direction) {
|
||||
case UP:
|
||||
uiDeviceSwipeUp(steps);
|
||||
break;
|
||||
case DOWN:
|
||||
uiDeviceSwipeDown(steps);
|
||||
break;
|
||||
case LEFT:
|
||||
uiDeviceSwipeLeft(steps);
|
||||
break;
|
||||
case RIGHT:
|
||||
uiDeviceSwipeRight(steps);
|
||||
break;
|
||||
case NULL:
|
||||
throw new Exception("No direction specified");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void uiObjectSwipe(UiObject view, Direction direction, int steps) throws Exception {
|
||||
switch (direction) {
|
||||
case UP:
|
||||
view.swipeUp(steps);
|
||||
break;
|
||||
case DOWN:
|
||||
view.swipeDown(steps);
|
||||
break;
|
||||
case LEFT:
|
||||
view.swipeLeft(steps);
|
||||
break;
|
||||
case RIGHT:
|
||||
view.swipeRight(steps);
|
||||
break;
|
||||
case NULL:
|
||||
throw new Exception("No direction specified");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void uiObjectVertPinchIn(UiObject view, int steps, int percent) throws Exception {
|
||||
final int FINGER_TOUCH_HALF_WIDTH = 20;
|
||||
|
||||
// Make value between 1 and 100
|
||||
int nPercent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent;
|
||||
float percentage = nPercent / 100f;
|
||||
|
||||
Rect rect = view.getVisibleBounds();
|
||||
|
||||
if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) {
|
||||
throw new IllegalStateException("Object width is too small for operation");
|
||||
}
|
||||
|
||||
// Start at the top-center and bottom-center of the control
|
||||
Point startPoint1 = new Point(rect.centerX(), rect.centerY()
|
||||
+ (int) ((rect.height() / 2) * percentage));
|
||||
Point startPoint2 = new Point(rect.centerX(), rect.centerY()
|
||||
- (int) ((rect.height() / 2) * percentage));
|
||||
|
||||
// End at the same point at the center of the control
|
||||
Point endPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH);
|
||||
Point endPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH);
|
||||
|
||||
view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
|
||||
}
|
||||
|
||||
public void uiObjectVertPinchOut(UiObject view, int steps, int percent) throws Exception {
|
||||
final int FINGER_TOUCH_HALF_WIDTH = 20;
|
||||
|
||||
// Make value between 1 and 100
|
||||
int nPercent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent;
|
||||
float percentage = nPercent / 100f;
|
||||
|
||||
Rect rect = view.getVisibleBounds();
|
||||
|
||||
if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) {
|
||||
throw new IllegalStateException("Object width is too small for operation");
|
||||
}
|
||||
|
||||
// Start from the same point at the center of the control
|
||||
Point startPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH);
|
||||
Point startPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH);
|
||||
|
||||
// End at the top-center and bottom-center of the control
|
||||
Point endPoint1 = new Point(rect.centerX(), rect.centerY()
|
||||
+ (int) ((rect.height() / 2) * percentage));
|
||||
Point endPoint2 = new Point(rect.centerX(), rect.centerY()
|
||||
- (int) ((rect.height() / 2) * percentage));
|
||||
|
||||
view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
|
||||
}
|
||||
|
||||
public void setScreenOrientation(ScreenOrientation orientation) throws Exception {
|
||||
switch (orientation) {
|
||||
case RIGHT:
|
||||
mDevice.setOrientationRight();
|
||||
break;
|
||||
case NATURAL:
|
||||
mDevice.setOrientationNatural();
|
||||
break;
|
||||
case LEFT:
|
||||
mDevice.setOrientationLeft();
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No orientation specified");
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetScreenOrientation() throws Exception {
|
||||
mDevice.unfreezeRotation();
|
||||
}
|
||||
|
||||
public void uiObjectPerformLongClick(UiObject view, int steps) throws Exception {
|
||||
Rect rect = view.getBounds();
|
||||
mDevice.swipe(rect.centerX(), rect.centerY(),
|
||||
rect.centerX(), rect.centerY(), steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeVertical(int startY, int endY, int xCoordinate, int steps) {
|
||||
mDevice.swipe(startY, xCoordinate, endY, xCoordinate, steps);
|
||||
}
|
||||
|
||||
public void uiDeviceSwipeHorizontal(int startX, int endX, int yCoordinate, int steps) {
|
||||
mDevice.swipe(startX, yCoordinate, endX, yCoordinate, steps);
|
||||
}
|
||||
|
||||
public void uiObjectPinch(UiObject view, PinchType direction, int steps,
|
||||
int percent) throws Exception {
|
||||
if (direction.equals(PinchType.IN)) {
|
||||
view.pinchIn(percent, steps);
|
||||
} else if (direction.equals(PinchType.OUT)) {
|
||||
view.pinchOut(percent, steps);
|
||||
}
|
||||
}
|
||||
|
||||
public void uiObjectVertPinch(UiObject view, PinchType direction,
|
||||
int steps, int percent) throws Exception {
|
||||
if (direction.equals(PinchType.IN)) {
|
||||
uiObjectVertPinchIn(view, steps, percent);
|
||||
} else if (direction.equals(PinchType.OUT)) {
|
||||
uiObjectVertPinchOut(view, steps, percent);
|
||||
}
|
||||
}
|
||||
|
||||
public void repeatClickUiObject(UiObject view, int repeatCount, int intervalInMillis) throws Exception {
|
||||
int repeatInterval = intervalInMillis > CLICK_REPEAT_INTERVAL_MINIMUM
|
||||
? intervalInMillis : CLICK_REPEAT_INTERVAL_DEFAULT;
|
||||
if (repeatCount < 1 || !view.isClickable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < repeatCount; ++i) {
|
||||
view.click();
|
||||
SystemClock.sleep(repeatInterval); // in order to register as separate click
|
||||
}
|
||||
}
|
||||
|
||||
public UiObject clickUiObject(FindByCriteria criteria, String matching) throws Exception {
|
||||
return clickUiObject(criteria, matching, null, false);
|
||||
}
|
||||
|
||||
public UiObject clickUiObject(FindByCriteria criteria, String matching, boolean wait) throws Exception {
|
||||
return clickUiObject(criteria, matching, null, wait);
|
||||
}
|
||||
|
||||
public UiObject clickUiObject(FindByCriteria criteria, String matching, String clazz) throws Exception {
|
||||
return clickUiObject(criteria, matching, clazz, false);
|
||||
}
|
||||
|
||||
public UiObject clickUiObject(FindByCriteria criteria, String matching, String clazz, boolean wait) throws Exception {
|
||||
UiObject view;
|
||||
|
||||
switch (criteria) {
|
||||
case BY_ID:
|
||||
view = (clazz == null)
|
||||
? getUiObjectByResourceId(matching) : getUiObjectByResourceId(matching, clazz);
|
||||
break;
|
||||
case BY_DESC:
|
||||
view = (clazz == null)
|
||||
? getUiObjectByDescription(matching) : getUiObjectByDescription(matching, clazz);
|
||||
break;
|
||||
case BY_TEXT:
|
||||
default:
|
||||
view = (clazz == null)
|
||||
? getUiObjectByText(matching) : getUiObjectByText(matching, clazz);
|
||||
break;
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
view.clickAndWaitForNewWindow();
|
||||
} else {
|
||||
view.click();
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByResourceId(String resourceId, String className) throws Exception {
|
||||
return getUiObjectByResourceId(resourceId, className, uiAutoTimeout);
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByResourceId(String resourceId, String className, long timeout) throws Exception {
|
||||
UiObject object = mDevice.findObject(new UiSelector().resourceId(resourceId)
|
||||
.className(className));
|
||||
if (!object.waitForExists(timeout)) {
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
resourceId, className));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByResourceId(String id) throws Exception {
|
||||
UiObject object = mDevice.findObject(new UiSelector().resourceId(id));
|
||||
|
||||
if (!object.waitForExists(uiAutoTimeout)) {
|
||||
throw new UiObjectNotFoundException("Could not find view with resource ID: " + id);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByDescription(String description, String className) throws Exception {
|
||||
return getUiObjectByDescription(description, className, uiAutoTimeout);
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByDescription(String description, String className, long timeout) throws Exception {
|
||||
UiObject object = mDevice.findObject(new UiSelector().descriptionContains(description)
|
||||
.className(className));
|
||||
if (!object.waitForExists(timeout)) {
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
description, className));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByDescription(String desc) throws Exception {
|
||||
UiObject object = mDevice.findObject(new UiSelector().descriptionContains(desc));
|
||||
|
||||
if (!object.waitForExists(uiAutoTimeout)) {
|
||||
throw new UiObjectNotFoundException("Could not find view with description: " + desc);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByText(String text, String className) throws Exception {
|
||||
return getUiObjectByText(text, className, uiAutoTimeout);
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByText(String text, String className, long timeout) throws Exception {
|
||||
UiObject object = mDevice.findObject(new UiSelector().textContains(text)
|
||||
.className(className));
|
||||
if (!object.waitForExists(timeout)) {
|
||||
throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"",
|
||||
text, className));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public UiObject getUiObjectByText(String text) throws Exception {
|
||||
UiObject object = mDevice.findObject(new UiSelector().textContains(text));
|
||||
|
||||
if (!object.waitForExists(uiAutoTimeout)) {
|
||||
throw new UiObjectNotFoundException("Could not find view with text: " + text);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
// Helper to select a folder in the gallery
|
||||
public void selectGalleryFolder(String directory) throws Exception {
|
||||
UiObject workdir =
|
||||
mDevice.findObject(new UiSelector().text(directory)
|
||||
.className("android.widget.TextView"));
|
||||
UiScrollable scrollView =
|
||||
new UiScrollable(new UiSelector().scrollable(true));
|
||||
|
||||
// If the folder is not present wait for a short time for
|
||||
// the media server to refresh its index.
|
||||
boolean discovered = workdir.waitForExists(TimeUnit.SECONDS.toMillis(10));
|
||||
if (!discovered && scrollView.exists()) {
|
||||
// First check if the directory is visible on the first
|
||||
// screen and if not scroll to the bottom of the screen to look for it.
|
||||
discovered = scrollView.scrollIntoView(workdir);
|
||||
|
||||
// If still not discovered scroll back to the top of the screen and
|
||||
// wait for a longer amount of time for the media server to refresh
|
||||
// its index.
|
||||
if (!discovered) {
|
||||
// scrollView.scrollToBeggining() doesn't work for this
|
||||
// particular scrollable view so use device method instead
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uiDeviceSwipeUp(20);
|
||||
}
|
||||
discovered = workdir.waitForExists(TimeUnit.SECONDS.toMillis(60));
|
||||
|
||||
// Scroll to the bottom of the screen one last time
|
||||
if (!discovered) {
|
||||
discovered = scrollView.scrollIntoView(workdir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discovered) {
|
||||
workdir.clickAndWaitForNewWindow();
|
||||
} else {
|
||||
throw new UiObjectNotFoundException("Could not find folder : " + directory);
|
||||
}
|
||||
}
|
||||
|
||||
// Override getParams function to decode a url encoded parameter bundle before
|
||||
// passing it to workloads.
|
||||
public Bundle getParams() {
|
||||
// Get the original parameter bundle
|
||||
parameters = getArguments();
|
||||
|
||||
// Decode each parameter in the bundle, except null values and "class", as this
|
||||
// used to control instrumentation and therefore not encoded.
|
||||
for (String key : parameters.keySet()) {
|
||||
String param = parameters.getString(key);
|
||||
if (param != null && !key.equals("class")) {
|
||||
param = android.net.Uri.decode(param);
|
||||
parameters = decode(parameters, key, param);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Helper function to decode a string and insert it as an appropriate type
|
||||
// into a provided bundle with its key.
|
||||
// Each bundle parameter will be a urlencoded string with 2 characters prefixed to the value
|
||||
// used to store the original type information, e.g. 'fl' -> list of floats.
|
||||
private Bundle decode(Bundle parameters, String key, String value) {
|
||||
char value_type = value.charAt(0);
|
||||
char value_dimension = value.charAt(1);
|
||||
String param = value.substring(2);
|
||||
|
||||
if (value_dimension == 's') {
|
||||
if (value_type == 's') {
|
||||
parameters.putString(key, param);
|
||||
} else if (value_type == 'f') {
|
||||
parameters.putFloat(key, Float.parseFloat(param));
|
||||
} else if (value_type == 'd') {
|
||||
parameters.putDouble(key, Double.parseDouble(param));
|
||||
} else if (value_type == 'b') {
|
||||
parameters.putBoolean(key, Boolean.parseBoolean(param));
|
||||
} else if (value_type == 'i') {
|
||||
parameters.putInt(key, Integer.parseInt(param));
|
||||
} else if (value_type == 'n') {
|
||||
parameters.putString(key, "None");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Error decoding:" + key + value
|
||||
+ " - unknown format");
|
||||
}
|
||||
} else if (value_dimension == 'l') {
|
||||
return decodeArray(parameters, key, value_type, param);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Error decoding:" + key + value
|
||||
+ " - unknown format");
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Helper function to deal with decoding arrays and update the bundle with
|
||||
// an appropriate array type. The string "0newelement0" is used to distinguish
|
||||
// each element for each other in the array when encoded.
|
||||
private Bundle decodeArray(Bundle parameters, String key, char type, String value) {
|
||||
String[] string_list = value.split("0newelement0");
|
||||
if (type == 's') {
|
||||
parameters.putStringArray(key, string_list);
|
||||
}
|
||||
else if (type == 'i') {
|
||||
int[] int_list = new int[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
int_list[i] = Integer.parseInt(string_list[i]);
|
||||
}
|
||||
parameters.putIntArray(key, int_list);
|
||||
} else if (type == 'f') {
|
||||
float[] float_list = new float[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
float_list[i] = Float.parseFloat(string_list[i]);
|
||||
}
|
||||
parameters.putFloatArray(key, float_list);
|
||||
} else if (type == 'd') {
|
||||
double[] double_list = new double[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
double_list[i] = Double.parseDouble(string_list[i]);
|
||||
}
|
||||
parameters.putDoubleArray(key, double_list);
|
||||
} else if (type == 'b') {
|
||||
boolean[] boolean_list = new boolean[string_list.length];
|
||||
for (int i = 0; i < string_list.length; i++){
|
||||
boolean_list[i] = Boolean.parseBoolean(string_list[i]);
|
||||
}
|
||||
parameters.putBooleanArray(key, boolean_list);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Error decoding array: " +
|
||||
value + " - unknown format");
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
}
|
35
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/UiAutoUtils.java
vendored
Normal file
35
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/UiAutoUtils.java
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/* Copyright 2013-2016 ARM Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.arm.wlauto.uiauto;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public final class UiAutoUtils {
|
||||
|
||||
/** Construct launch command of an application. */
|
||||
public static String createLaunchCommand(Bundle parameters) {
|
||||
String launchCommand;
|
||||
String activityName = parameters.getString("launch_activity");
|
||||
String packageName = parameters.getString("package_name");
|
||||
if (activityName.equals("None")) {
|
||||
launchCommand = String.format("am start --user -3 %s", packageName);
|
||||
}
|
||||
else {
|
||||
launchCommand = String.format("am start --user -3 -n %s/%s", packageName, activityName);
|
||||
}
|
||||
return launchCommand;
|
||||
}
|
||||
}
|
70
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/UxPerfUiAutomation.java
vendored
Normal file
70
wlauto/external/uiauto/app/src/main/java/com/arm/wlauto/uiauto/UxPerfUiAutomation.java
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
/* Copyright 2013-2016 ARM Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.arm.wlauto.uiauto;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class UxPerfUiAutomation extends BaseUiAutomation {
|
||||
|
||||
protected String packageName;
|
||||
protected String packageID;
|
||||
|
||||
//Get application package parameters and create package ID
|
||||
public void getPackageParameters() {
|
||||
packageName = parameters.getString("package_name");
|
||||
packageID = packageName + ":id/";
|
||||
}
|
||||
|
||||
public void setWorkloadParameters(Bundle parameters, String packageName, String packageID){
|
||||
this.parameters = parameters;
|
||||
this.packageName = packageName;
|
||||
this.packageID = packageID;
|
||||
}
|
||||
|
||||
public String getPackageID(){
|
||||
return packageID;
|
||||
}
|
||||
|
||||
private Logger logger = Logger.getLogger(UxPerfUiAutomation.class.getName());
|
||||
|
||||
public enum GestureType { UIDEVICE_SWIPE, UIOBJECT_SWIPE, PINCH };
|
||||
|
||||
public static class GestureTestParams {
|
||||
public GestureType gestureType;
|
||||
public Direction gestureDirection;
|
||||
public PinchType pinchType;
|
||||
public int percent;
|
||||
public int steps;
|
||||
|
||||
public GestureTestParams(GestureType gesture, Direction direction, int steps) {
|
||||
this.gestureType = gesture;
|
||||
this.gestureDirection = direction;
|
||||
this.pinchType = PinchType.NULL;
|
||||
this.steps = steps;
|
||||
this.percent = 0;
|
||||
}
|
||||
|
||||
public GestureTestParams(GestureType gesture, PinchType pinchType, int steps, int percent) {
|
||||
this.gestureType = gesture;
|
||||
this.gestureDirection = Direction.NULL;
|
||||
this.pinchType = pinchType;
|
||||
this.steps = steps;
|
||||
this.percent = percent;
|
||||
}
|
||||
}
|
||||
}
|
24
wlauto/external/uiauto/build.gradle
vendored
Normal file
24
wlauto/external/uiauto/build.gradle
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.2'
|
||||
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
16
wlauto/external/uiauto/build.sh
vendored
16
wlauto/external/uiauto/build.sh
vendored
@ -14,8 +14,18 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# Ensure gradelw exists before starting
|
||||
if [[ ! -f gradlew ]]; then
|
||||
echo 'gradlew file not found! Check that you are in the right directory.'
|
||||
exit 9
|
||||
fi
|
||||
|
||||
# Build and return appropriate exit code if failed
|
||||
./gradlew clean :app:assembleDebug
|
||||
exit_code=$?
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "ERROR: 'gradle build' exited with code $exit_code"
|
||||
exit $exit_code
|
||||
fi
|
||||
|
||||
ant build
|
||||
|
||||
cp bin/classes/com/arm/wlauto/uiauto/BaseUiAutomation.class ../../common
|
||||
cp app/build/outputs/aar/app-debug.aar ../../common/android/uiauto.aar
|
||||
|
92
wlauto/external/uiauto/build.xml
vendored
92
wlauto/external/uiauto/build.xml
vendored
@ -1,92 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="com.arm.wlauto.uiauto" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties" />
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties" />
|
||||
|
||||
<!-- if sdk.dir was not set from one of the property file, then
|
||||
get it from the ANDROID_HOME env var.
|
||||
This must be done before we load project.properties since
|
||||
the proguard config can use sdk.dir -->
|
||||
<property environment="env" />
|
||||
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||
<isset property="env.ANDROID_HOME" />
|
||||
</condition>
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties" />
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: VERSION_TAG -->
|
||||
<import file="${sdk.dir}/tools/ant/uibuild.xml" />
|
||||
|
||||
</project>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user