mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-09-05 04:42:37 +01:00
Compare commits
582 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 |
17
.gitignore
vendored
17
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
*.bak
|
*.bak
|
||||||
*.o
|
*.o
|
||||||
*.cmd
|
*.cmd
|
||||||
|
*.iml
|
||||||
Module.symvers
|
Module.symvers
|
||||||
modules.order
|
modules.order
|
||||||
*~
|
*~
|
||||||
@@ -14,9 +15,12 @@ wa_output/
|
|||||||
doc/source/api/
|
doc/source/api/
|
||||||
doc/source/extensions/
|
doc/source/extensions/
|
||||||
MANIFEST
|
MANIFEST
|
||||||
wlauto/external/uiautomator/bin/
|
wlauto/external/uiauto/**/build/
|
||||||
wlauto/external/uiautomator/*.properties
|
wlauto/external/uiauto/*.properties
|
||||||
wlauto/external/uiautomator/build.xml
|
wlauto/external/uiauto/app/libs/
|
||||||
|
wlauto/external/uiauto/app/proguard-rules.pro
|
||||||
|
wlauto/external/uiauto/**/.gradle
|
||||||
|
wlauto/external/uiauto/**/.idea
|
||||||
*.orig
|
*.orig
|
||||||
local.properties
|
local.properties
|
||||||
wlauto/external/revent/libs/
|
wlauto/external/revent/libs/
|
||||||
@@ -27,4 +31,9 @@ pmu_logger.mod.c
|
|||||||
.tmp_versions
|
.tmp_versions
|
||||||
obj/
|
obj/
|
||||||
libs/armeabi
|
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
|
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
|
Workload Automation (WA) is a framework for executing workloads and collecting
|
||||||
measurements on Android and Linux devices. WA includes automation for nearly 50
|
measurements on Android and Linux devices. WA includes automation for nearly 50
|
||||||
workloads (mostly Android), some common instrumentation (ftrace, ARM
|
workloads (mostly Android), some common instrumentation (ftrace, ARM
|
||||||
@@ -46,7 +59,8 @@ documentation.
|
|||||||
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
|
Documentation in reStructuredText format may be found under ``doc/source``. To
|
||||||
compile it into cross-linked HTML, make sure you have `Sphinx
|
compile it into cross-linked HTML, make sure you have `Sphinx
|
||||||
|
@@ -6,6 +6,11 @@ distributed as part of WA releases.
|
|||||||
Scripts
|
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
|
:clean_install: Performs a clean install of WA from source. This will remove any
|
||||||
existing WA install (regardless of whether it was made from
|
existing WA install (regardless of whether it was made from
|
||||||
source or through a tarball with pip).
|
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.exceptions import WAError, ToolError
|
||||||
from wlauto.utils.doc import format_simple_table
|
from wlauto.utils.doc import format_simple_table
|
||||||
|
from wlauto.utils.misc import check_output, get_null, which
|
||||||
|
|
||||||
|
|
||||||
def get_aapt_path():
|
def get_aapt_path():
|
||||||
"""Return the full path to aapt tool."""
|
"""Return the full path to aapt tool."""
|
||||||
sdk_path = os.getenv('ANDROID_HOME')
|
sdk_path = os.getenv('ANDROID_HOME')
|
||||||
if not sdk_path:
|
if 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')
|
||||||
build_tools_directory = os.path.join(sdk_path, 'build-tools')
|
versions = os.listdir(build_tools_directory)
|
||||||
versions = os.listdir(build_tools_directory)
|
for version in reversed(sorted(versions)):
|
||||||
for version in reversed(sorted(versions)):
|
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
||||||
aapt_path = os.path.join(build_tools_directory, version, 'aapt')
|
if os.path.isfile(aapt_path):
|
||||||
if os.path.isfile(aapt_path):
|
logging.debug('Found aapt for version {}'.format(version))
|
||||||
logging.debug('Found aapt for version {}'.format(version))
|
return aapt_path
|
||||||
return aapt_path
|
else:
|
||||||
|
raise ToolError('aapt not found. Please make sure at least one Android platform is installed.')
|
||||||
else:
|
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):
|
def get_apks(path):
|
||||||
|
59
doc/Makefile
59
doc/Makefile
@@ -7,18 +7,11 @@ SPHINXBUILD = sphinx-build
|
|||||||
PAPER =
|
PAPER =
|
||||||
BUILDDIR = build
|
BUILDDIR = build
|
||||||
|
|
||||||
SPHINXAPI = sphinx-apidoc
|
|
||||||
SPHINXAPIOPTS =
|
|
||||||
|
|
||||||
WAEXT = ./build_extension_docs.py
|
|
||||||
WAEXTOPTS = source/extensions ../wlauto ../wlauto/external ../wlauto/tests
|
|
||||||
|
|
||||||
|
|
||||||
# Internal variables.
|
# Internal variables.
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
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
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||||
|
|
||||||
@@ -58,52 +51,38 @@ coverage:
|
|||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The coverage reports are in $(BUILDDIR)/coverage."
|
@echo "Build finished. The coverage reports are in $(BUILDDIR)/coverage."
|
||||||
|
|
||||||
api: ../wlauto
|
html:
|
||||||
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
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
dirhtml: api waext sigtab
|
dirhtml:
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
singlehtml: api waext sigtab
|
singlehtml:
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
pickle: api waext sigtab
|
pickle:
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can process the pickle files."
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
json: api waext sigtab
|
json:
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can process the JSON files."
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
htmlhelp: api waext sigtab
|
htmlhelp:
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
qthelp: api waext sigtab
|
qthelp:
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
@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 "To view the help file:"
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WorkloadAutomation2.qhc"
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WorkloadAutomation2.qhc"
|
||||||
|
|
||||||
devhelp: api
|
devhelp:
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished."
|
@echo "Build finished."
|
||||||
@@ -121,64 +100,64 @@ devhelp: api
|
|||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WorkloadAutomation2"
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WorkloadAutomation2"
|
||||||
@echo "# devhelp"
|
@echo "# devhelp"
|
||||||
|
|
||||||
epub: api waext sigtab
|
epub:
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
latex: api waext sigtab
|
latex:
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
latexpdf: api waext sigtab
|
latexpdf:
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
text: api waext sigtab
|
text:
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
man: api waext sigtab
|
man:
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
texinfo: api waext sigtab
|
texinfo:
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
"(use \`make info' here to do that automatically)."
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
info: api waext sigtab
|
info:
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
make -C $(BUILDDIR)/texinfo info
|
make -C $(BUILDDIR)/texinfo info
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
gettext: api waext sigtab
|
gettext:
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
changes: api waext sigtab
|
changes:
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
@echo
|
@echo
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
linkcheck: api waext sigtab
|
linkcheck:
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
@echo
|
@echo
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
doctest: api waext sigtab
|
doctest:
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
from wlauto import ExtensionLoader
|
from wlauto import ExtensionLoader
|
||||||
from wlauto.utils.doc import get_rst_from_extension, underline
|
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 = ExtensionLoader(keep_going=True)
|
||||||
loader.clear()
|
loader.clear()
|
||||||
loader.update(paths=[source_dir], ignore_paths=ignore_paths)
|
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:
|
for ext_type in loader.extension_kinds:
|
||||||
if not ext_type in GENERATE_FOR:
|
if not ext_type in GENERATE_FOR:
|
||||||
continue
|
continue
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import string
|
import string
|
||||||
from copy import copy
|
|
||||||
|
|
||||||
from wlauto.core.instrumentation import SIGNAL_MAP, PRIORITY_MAP
|
from wlauto.core.instrumentation import SIGNAL_MAP, PRIORITY_MAP
|
||||||
from wlauto.utils.doc import format_simple_table
|
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',
|
CONVINIENCE_ALIASES = ['initialize', 'setup', 'start', 'stop', 'process_workload_result',
|
||||||
'update_result', 'teardown', 'finalize']
|
'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):
|
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()],
|
signal_table = format_simple_table([(k, v) for k, v in SIGNAL_MAP.iteritems()],
|
||||||
headers=['method name', 'signal'], align='<<')
|
headers=['method name', 'signal'], align='<<')
|
||||||
priority_table = format_simple_table([(escape_trailing_underscore(k), v) for k, v in PRIORITY_MAP.iteritems()],
|
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:
|
with open(OUTPUT_TEMPLATE_FILE) as fh:
|
||||||
template = string.Template(fh.read())
|
template = string.Template(fh.read())
|
||||||
with open(outfile, 'w') as wfh:
|
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
|
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
|
In addition to configuring the workload itself, we can also specify
|
||||||
configuration for the underlying device. This can be done by setting runtime
|
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,228 @@
|
|||||||
=================================
|
=================================
|
||||||
What's New in Workload Automation
|
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
|
Version 2.5.0
|
||||||
|
@@ -28,12 +28,16 @@
|
|||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
import warnings
|
import warnings
|
||||||
|
from sphinx.apidoc import main
|
||||||
|
|
||||||
warnings.filterwarnings('ignore', "Module louie was already imported")
|
warnings.filterwarnings('ignore', "Module louie was already imported")
|
||||||
|
|
||||||
this_dir = os.path.dirname(__file__)
|
this_dir = os.path.dirname(__file__)
|
||||||
|
sys.path.insert(0, os.path.join(this_dir, '..'))
|
||||||
sys.path.insert(0, os.path.join(this_dir, '../..'))
|
sys.path.insert(0, os.path.join(this_dir, '../..'))
|
||||||
import wlauto
|
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,
|
# 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
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
@@ -264,7 +268,21 @@ texinfo_documents = [
|
|||||||
#texinfo_show_urls = 'footnote'
|
#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):
|
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',
|
app.add_object_type('confval', 'confval',
|
||||||
objname='configuration value',
|
objname='configuration value',
|
||||||
indextemplate='pair: %s; configuration value')
|
indextemplate='pair: %s; configuration value')
|
||||||
|
@@ -118,6 +118,7 @@ and detailed descriptions of how WA functions under the hood.
|
|||||||
additional_topics
|
additional_topics
|
||||||
daq_device_setup
|
daq_device_setup
|
||||||
revent
|
revent
|
||||||
|
apk_workloads
|
||||||
contributing
|
contributing
|
||||||
|
|
||||||
API Reference
|
API Reference
|
||||||
|
@@ -192,10 +192,28 @@ version $version".
|
|||||||
Some WA extensions have additional dependencies that need to be
|
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
|
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
|
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
|
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.
|
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
|
APK Files
|
||||||
---------
|
---------
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
.. _invocation:
|
.. _invocation:
|
||||||
|
.. highlight:: none
|
||||||
|
|
||||||
========
|
========
|
||||||
Commands
|
Commands
|
||||||
@@ -15,7 +16,7 @@ Individual sub-commands are discussed in detail below.
|
|||||||
run
|
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
|
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
|
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
|
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
|
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
|
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.
|
--debug Enable debug mode. Note: this implies --verbose.
|
||||||
-d DIR, --output-directory DIR
|
-d DIR, --output-directory DIR
|
||||||
Specify a directory where the output will be
|
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
|
will abort unless -f option (see below) is used,in
|
||||||
which case the contents of the directory will be
|
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.
|
wa_output will be used instead.
|
||||||
-f, --force Overwrite output directory if it exists. By default,
|
-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.
|
accidental data loss.
|
||||||
-i ID, --id ID Specify a workload spec ID from an agenda to run. If
|
-i ID, --id ID Specify a workload spec ID from an agenda to run. If
|
||||||
this is specified, only that particular spec will be
|
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.
|
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
|
list
|
||||||
----
|
----
|
||||||
|
|
||||||
This lists all extensions of a particular type. For example ::
|
This lists all extensions of a particular type. For example::
|
||||||
|
|
||||||
wa list workloads
|
wa list workloads
|
||||||
|
|
||||||
@@ -97,11 +162,11 @@ show
|
|||||||
|
|
||||||
This will show detailed information about an extension, including more in-depth
|
This will show detailed information about an extension, including more in-depth
|
||||||
description and any parameters/configuration that are available. For example
|
description and any parameters/configuration that are available. For example
|
||||||
executing ::
|
executing::
|
||||||
|
|
||||||
wa show andebench
|
wa show andebench
|
||||||
|
|
||||||
will produce something like ::
|
will produce something like::
|
||||||
|
|
||||||
|
|
||||||
andebench
|
andebench
|
||||||
@@ -148,7 +213,7 @@ either ``setup`` or ``run``. This should be specified with the ``-s``
|
|||||||
argument. The full set of options for this command are::
|
argument. The full set of options for this command are::
|
||||||
|
|
||||||
usage: wa record [-h] [-c CONFIG] [-v] [--debug] [--version] [-d DEVICE]
|
usage: wa record [-h] [-c CONFIG] [-v] [--debug] [--version] [-d DEVICE]
|
||||||
[-s SUFFIX] [-o OUTPUT] [-p PACKAGE] [-C]
|
[-s SUFFIX] [-o OUTPUT] [-p PACKAGE] [-g] [-C]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@@ -165,6 +230,7 @@ argument. The full set of options for this command are::
|
|||||||
Directory to save the recording in
|
Directory to save the recording in
|
||||||
-p PACKAGE, --package PACKAGE
|
-p PACKAGE, --package PACKAGE
|
||||||
Package to launch before recording
|
Package to launch before recording
|
||||||
|
-g, --gamepad Record from a gamepad rather than all devices.
|
||||||
-C, --clear Clear app cache before launching it
|
-C, --clear Clear app cache before launching it
|
||||||
|
|
||||||
.. _replay-command:
|
.. _replay-command:
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
.. _revent_files_creation:
|
.. _revent_files_creation:
|
||||||
|
|
||||||
revent
|
revent
|
||||||
======
|
++++++
|
||||||
|
|
||||||
|
Overview and Usage
|
||||||
|
==================
|
||||||
|
|
||||||
revent utility can be used to record and later play back a sequence of user
|
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
|
input events, such as key presses and touch screen taps. This is an alternative
|
||||||
@@ -42,6 +45,10 @@ 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
|
recording revent files for a ``GameWorkload`` you can use the ``-s`` option to
|
||||||
add ``run`` or ``setup`` suffixes.
|
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`
|
For more information run please read :ref:`record-command`
|
||||||
|
|
||||||
|
|
||||||
@@ -55,6 +62,7 @@ replay::
|
|||||||
|
|
||||||
For more information run please read :ref:`replay-command`
|
For more information run please read :ref:`replay-command`
|
||||||
|
|
||||||
|
|
||||||
Using revent With Workloads
|
Using revent With Workloads
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@@ -106,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
|
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,
|
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).
|
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
|
the locations scanned by the resource resolver in a directry structure
|
||||||
``<root>/bin/<abi>/<binary>`` (where ``root`` is the base resource location to
|
``<root>/bin/<abi>/<binary>`` (where ``root`` is the base resource location to
|
||||||
be searched, e.g. ``~/.workload_automation/depencencies/<extension name>``, and
|
be searched, e.g. ``~/.workload_automation/depencencies/<extension name>``, and
|
||||||
``<abi>`` is the ABI for which the exectuable has been compiled, as returned by
|
``<abi>`` is the ABI for which the exectuable has been compiled, as returned by
|
||||||
``self.device.abi``).
|
``self.device.abi``).
|
||||||
|
|
||||||
Once the path to the host-side binary has been obtained, it may be deployed using
|
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``.
|
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
|
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
|
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
|
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.
|
name in ``PATH`` is the version deployed by WA.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
@@ -964,7 +964,7 @@ that accompanies them, in addition to what has been outlined here, should
|
|||||||
provide enough guidance.
|
provide enough guidance.
|
||||||
|
|
||||||
:commands: This allows extending WA with additional sub-commands (to supplement
|
: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
|
:modules: Modules are "extensions for extensions". They can be loaded by other
|
||||||
extensions to expand their functionality (for example, a flashing
|
extensions to expand their functionality (for example, a flashing
|
||||||
module maybe loaded by a device in order to support 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,
|
packages=packages,
|
||||||
package_data=data_files,
|
package_data=data_files,
|
||||||
scripts=scripts,
|
scripts=scripts,
|
||||||
url='N/A',
|
url='http://github.com/arm-sowftware/workload-automation',
|
||||||
license='Apache v2',
|
license='Apache v2',
|
||||||
maintainer='ARM Architecture & Technology Device Lab',
|
maintainer='ARM Architecture & Technology Device Lab',
|
||||||
maintainer_email='workload-automation@arm.com',
|
maintainer_email='workload-automation@arm.com',
|
||||||
@@ -80,6 +80,7 @@ params = dict(
|
|||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
'other': ['jinja2', 'pandas>=0.13.1'],
|
'other': ['jinja2', 'pandas>=0.13.1'],
|
||||||
|
'statedetect': ['numpy', 'imutils', 'opencv-python'],
|
||||||
'test': ['nose'],
|
'test': ['nose'],
|
||||||
'mongodb': ['pymongo'],
|
'mongodb': ['pymongo'],
|
||||||
'notify': ['notify2'],
|
'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.device import AndroidDevice, BigLittleDevice # NOQA
|
||||||
from wlauto.common.android.resources import ApkFile, JarFile
|
from wlauto.common.android.resources import ApkFile, JarFile
|
||||||
from wlauto.common.android.workload import (UiAutomatorWorkload, ApkWorkload, AndroidBenchmark, # NOQA
|
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
|
from wlauto.core.version import get_wa_version
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# This agenda specifies configuration that may be used for regression runs
|
# This agenda specifies configuration that may be used for regression runs
|
||||||
# on big.LITTLE systems. This agenda will with a TC2 device configured as
|
# on big.LITTLE systems. This agenda will work with a TC2 device configured
|
||||||
# described in the documentation.
|
# as described in the documentation.
|
||||||
config:
|
config:
|
||||||
device: tc2
|
device: tc2
|
||||||
run_name: big.LITTLE_regression
|
run_name: big.LITTLE_regression
|
||||||
@@ -69,7 +69,7 @@ workloads:
|
|||||||
- id: b10
|
- id: b10
|
||||||
name: smartbench
|
name: smartbench
|
||||||
- id: b11
|
- id: b11
|
||||||
name: sqlite
|
name: sqlitebm
|
||||||
- id: b12
|
- id: b12
|
||||||
name: vellamo
|
name: vellamo
|
||||||
|
|
||||||
|
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -22,7 +22,6 @@ import textwrap
|
|||||||
import argparse
|
import argparse
|
||||||
import shutil
|
import shutil
|
||||||
import getpass
|
import getpass
|
||||||
import subprocess
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@@ -30,8 +29,9 @@ import yaml
|
|||||||
from wlauto import ExtensionLoader, Command, settings
|
from wlauto import ExtensionLoader, Command, settings
|
||||||
from wlauto.exceptions import CommandError, ConfigError
|
from wlauto.exceptions import CommandError, ConfigError
|
||||||
from wlauto.utils.cli import init_argument_parser
|
from wlauto.utils.cli import init_argument_parser
|
||||||
from wlauto.utils.misc import (capitalize, check_output,
|
from wlauto.utils.misc import (capitalize,
|
||||||
ensure_file_directory_exists as _f, ensure_directory_exists as _d)
|
ensure_file_directory_exists as _f,
|
||||||
|
ensure_directory_exists as _d)
|
||||||
from wlauto.utils.types import identifier
|
from wlauto.utils.types import identifier
|
||||||
from wlauto.utils.doc import format_body
|
from wlauto.utils.doc import format_body
|
||||||
|
|
||||||
@@ -41,20 +41,6 @@ __all__ = ['create_workload']
|
|||||||
|
|
||||||
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates')
|
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):
|
class CreateSubcommand(object):
|
||||||
|
|
||||||
@@ -321,7 +307,7 @@ def create_basic_workload(path, name, class_name):
|
|||||||
|
|
||||||
|
|
||||||
def create_uiautomator_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)
|
create_uiauto_project(uiauto_path, name)
|
||||||
source_file = os.path.join(path, '__init__.py')
|
source_file = os.path.join(path, '__init__.py')
|
||||||
with open(source_file, 'w') as wfh:
|
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):
|
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)
|
create_uiauto_project(uiauto_path, name)
|
||||||
source_file = os.path.join(path, '__init__.py')
|
source_file = os.path.join(path, '__init__.py')
|
||||||
with open(source_file, 'w') as wfh:
|
with open(source_file, 'w') as wfh:
|
||||||
wfh.write(render_template('android_uiauto_benchmark', {'name': name, 'class_name': class_name}))
|
wfh.write(render_template('android_uiauto_benchmark', {'name': name, 'class_name': class_name}))
|
||||||
|
|
||||||
|
|
||||||
def create_uiauto_project(path, name, target='1'):
|
def create_uiauto_project(path, name):
|
||||||
sdk_path = get_sdk_path()
|
|
||||||
android_path = os.path.join(sdk_path, 'tools', 'android')
|
|
||||||
package_name = 'com.arm.wlauto.uiauto.' + name.lower()
|
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
|
shutil.copytree(os.path.join(TEMPLATES_DIR, 'uiauto_template'), path)
|
||||||
command = '{} create uitest-project --name {} --target {} --path {}'.format(android_path,
|
|
||||||
package_name,
|
manifest_path = os.path.join(path, 'app', 'src', 'main')
|
||||||
target,
|
mainifest = os.path.join(_d(manifest_path), 'AndroidManifest.xml')
|
||||||
path)
|
with open(mainifest, 'w') as wfh:
|
||||||
try:
|
wfh.write(render_template('uiauto_AndroidManifest.xml', {'package_name': package_name}))
|
||||||
check_output(command, shell=True)
|
|
||||||
except subprocess.CalledProcessError as e:
|
build_gradle_path = os.path.join(path, 'app')
|
||||||
if 'is is not valid' in e.output:
|
build_gradle = os.path.join(_d(build_gradle_path), 'build.gradle')
|
||||||
message = 'No Android SDK target found; have you run "{} update sdk" and download a platform?'
|
with open(build_gradle, 'w') as wfh:
|
||||||
raise CommandError(message.format(android_path))
|
wfh.write(render_template('uiauto_build.gradle', {'package_name': package_name}))
|
||||||
|
|
||||||
build_script = os.path.join(path, 'build.sh')
|
build_script = os.path.join(path, 'build.sh')
|
||||||
with open(build_script, 'w') as wfh:
|
with open(build_script, 'w') as wfh:
|
||||||
template = string.Template(UIAUTO_BUILD_SCRIPT)
|
wfh.write(render_template('uiauto_build_script', {'package_name': package_name}))
|
||||||
wfh.write(template.substitute({'package_name': package_name}))
|
|
||||||
os.chmod(build_script, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
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]),
|
os.sep.join(package_name.split('.')[:-1]),
|
||||||
'UiAutomation.java'))
|
'UiAutomation.java'))
|
||||||
with open(source_file, 'w') as wfh:
|
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 os
|
||||||
import sys
|
import sys
|
||||||
|
import signal
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
from wlauto import ExtensionLoader, Command, settings
|
from wlauto import ExtensionLoader, Command, settings
|
||||||
from wlauto.common.resources import Executable
|
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.resolver import ResourceResolver
|
||||||
from wlauto.core.configuration import RunConfiguration
|
from wlauto.core.configuration import RunConfiguration
|
||||||
from wlauto.core.agenda import Agenda
|
from wlauto.core.agenda import Agenda
|
||||||
|
from wlauto.utils.revent import ReventRecording, GAMEPAD_MODE
|
||||||
|
|
||||||
|
|
||||||
class RecordCommand(Command):
|
class ReventCommand(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")
|
|
||||||
|
|
||||||
# Validate command options
|
# Validate command options
|
||||||
def validate_args(self, args):
|
def validate_args(self, args):
|
||||||
@@ -89,21 +61,83 @@ class RecordCommand(Command):
|
|||||||
self.device.initialize(context)
|
self.device.initialize(context)
|
||||||
|
|
||||||
host_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
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)
|
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):
|
def run(self, args):
|
||||||
if args.device:
|
if args.device:
|
||||||
self.device_name = args.device
|
device_name = args.device
|
||||||
else:
|
else:
|
||||||
self.device_name = self.device.get_device_model()
|
device_name = self.device.get_device_model()
|
||||||
|
|
||||||
if args.suffix:
|
if args.suffix:
|
||||||
args.suffix += "."
|
args.suffix += "."
|
||||||
|
|
||||||
revent_file = self.device.path.join(self.device.working_directory,
|
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:
|
if args.clear:
|
||||||
self.device.execute("pm clear {}".format(args.package))
|
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...")
|
self.logger.info("Press Enter when you are ready to record...")
|
||||||
raw_input("")
|
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.device.kick_off(command)
|
||||||
|
|
||||||
self.logger.info("Press Enter when you have finished recording...")
|
self.logger.info("Press Enter when you have finished recording...")
|
||||||
raw_input("")
|
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.logger.info("Pulling files from device")
|
||||||
self.device.pull_file(revent_file, args.output or os.getcwdu())
|
self.device.pull_file(revent_file, args.output or os.getcwdu())
|
||||||
|
|
||||||
|
|
||||||
class ReplayCommand(RecordCommand):
|
class ReplayCommand(ReventCommand):
|
||||||
|
|
||||||
name = 'replay'
|
name = 'replay'
|
||||||
description = '''Replay a revent recording
|
description = '''Replay a revent recording
|
||||||
@@ -154,8 +194,13 @@ class ReplayCommand(RecordCommand):
|
|||||||
self.logger.info("Starting {}".format(args.package))
|
self.logger.info("Starting {}".format(args.package))
|
||||||
self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.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)
|
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")
|
self.logger.info("Finished replay")
|
||||||
|
|
||||||
|
|
||||||
|
@@ -111,4 +111,3 @@ def format_extension_parameters(extension, out, width, shift=4):
|
|||||||
param_texts.append(indent(param_text, shift))
|
param_texts.append(indent(param_text, shift))
|
||||||
|
|
||||||
out.write(format_column('\n'.join(param_texts), width))
|
out.write(format_column('\n'.join(param_texts), width))
|
||||||
|
|
||||||
|
@@ -2,23 +2,30 @@ package ${package_name};
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
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.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
// Import the uiautomator libraries
|
// Import the uiautomator libraries
|
||||||
import com.android.uiautomator.core.UiObject;
|
import android.support.test.uiautomator.UiObject;
|
||||||
import com.android.uiautomator.core.UiObjectNotFoundException;
|
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||||
import com.android.uiautomator.core.UiScrollable;
|
import android.support.test.uiautomator.UiScrollable;
|
||||||
import com.android.uiautomator.core.UiSelector;
|
import android.support.test.uiautomator.UiSelector;
|
||||||
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
|
|
||||||
|
|
||||||
import com.arm.wlauto.uiauto.BaseUiAutomation;
|
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 static String TAG = "${name}";
|
||||||
|
|
||||||
public void runUiAutomation() throws Exception {
|
@Test
|
||||||
|
public void runUiAutomation() throws Exception {
|
||||||
|
initialize_instrumentation();
|
||||||
// UI Automation code goes here
|
// UI Automation code goes here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'
|
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
Binary file not shown.
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ import tempfile
|
|||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
import json
|
import json
|
||||||
|
import xml.dom.minidom
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
|
||||||
from wlauto.core.extension import Parameter
|
from wlauto.core.extension import Parameter
|
||||||
@@ -29,7 +30,7 @@ from wlauto.common.resources import Executable
|
|||||||
from wlauto.core.resource import NO_ONE
|
from wlauto.core.resource import NO_ONE
|
||||||
from wlauto.common.linux.device import BaseLinuxDevice, PsEntry
|
from wlauto.common.linux.device import BaseLinuxDevice, PsEntry
|
||||||
from wlauto.exceptions import DeviceError, WorkerThreadError, TimeoutError, DeviceNotRespondingError
|
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.types import boolean, regex
|
||||||
from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices,
|
from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices,
|
||||||
adb_command, AndroidProperties, ANDROID_VERSION_MAP)
|
adb_command, AndroidProperties, ANDROID_VERSION_MAP)
|
||||||
@@ -52,9 +53,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
description='The unique ID of the device as output by "adb devices".'),
|
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),
|
Parameter('android_prompt', kind=regex, default=re.compile('^.*(shell|root)@.*:/\S* [#$] ', re.MULTILINE),
|
||||||
description='The format of matching the shell prompt in Android.'),
|
description='The format of matching the shell prompt in Android.'),
|
||||||
Parameter('working_directory', default='/sdcard/wa-working',
|
Parameter('working_directory', default='/sdcard/wa-working', override=True),
|
||||||
description='Directory that will be used WA on the device for output files etc.'),
|
Parameter('binaries_directory', default='/data/local/tmp/wa-bin', override=True,
|
||||||
Parameter('binaries_directory', default='/data/local/tmp', override=True,
|
|
||||||
description='Location of binaries on the device.'),
|
description='Location of binaries on the device.'),
|
||||||
Parameter('package_data_directory', default='/data/data',
|
Parameter('package_data_directory', default='/data/data',
|
||||||
description='Location of of data for an installed package (APK).'),
|
description='Location of of data for an installed package (APK).'),
|
||||||
@@ -108,19 +108,34 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def abi(self):
|
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
|
@property
|
||||||
def supported_eabi(self):
|
def supported_abi(self):
|
||||||
props = self.getprop()
|
props = self.getprop()
|
||||||
result = [props['ro.product.cpu.abi']]
|
result = [props['ro.product.cpu.abi']]
|
||||||
if 'ro.product.cpu.abi2' in props:
|
if 'ro.product.cpu.abi2' in props:
|
||||||
result.append(props['ro.product.cpu.abi2'])
|
result.append(props['ro.product.cpu.abi2'])
|
||||||
if 'ro.product.cpu.abilist' in props:
|
if 'ro.product.cpu.abilist' in props:
|
||||||
for eabi in props['ro.product.cpu.abilist'].split(','):
|
for abi in props['ro.product.cpu.abilist'].split(','):
|
||||||
if eabi not in result:
|
if abi not in result:
|
||||||
result.append(eabi)
|
result.append(abi)
|
||||||
return result
|
|
||||||
|
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):
|
def __init__(self, **kwargs):
|
||||||
super(AndroidDevice, self).__init__(**kwargs)
|
super(AndroidDevice, self).__init__(**kwargs)
|
||||||
@@ -262,6 +277,24 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
return line.split('=', 1)[1]
|
return line.split('=', 1)[1]
|
||||||
return None
|
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):
|
def list_packages(self):
|
||||||
"""
|
"""
|
||||||
List packages installed on the device.
|
List packages installed on the device.
|
||||||
@@ -342,7 +375,7 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
|
|
||||||
def delete_file(self, filepath, as_root=False): # pylint: disable=W0221
|
def delete_file(self, filepath, as_root=False): # pylint: disable=W0221
|
||||||
self._check_ready()
|
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):
|
def file_exists(self, filepath):
|
||||||
self._check_ready()
|
self._check_ready()
|
||||||
@@ -350,18 +383,26 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
timeout=self.default_timeout)
|
timeout=self.default_timeout)
|
||||||
return bool(int(output))
|
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()
|
ext = os.path.splitext(filepath)[1].lower()
|
||||||
if ext == '.apk':
|
if ext == '.apk':
|
||||||
return self.install_apk(filepath, timeout)
|
return self.install_apk(filepath, timeout, replace)
|
||||||
else:
|
else:
|
||||||
return self.install_executable(filepath, with_name)
|
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()
|
self._check_ready()
|
||||||
ext = os.path.splitext(filepath)[1].lower()
|
ext = os.path.splitext(filepath)[1].lower()
|
||||||
if ext == '.apk':
|
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:
|
else:
|
||||||
raise DeviceError('Can\'t install {}: unsupported format.'.format(filepath))
|
raise DeviceError('Can\'t install {}: unsupported format.'.format(filepath))
|
||||||
|
|
||||||
@@ -467,8 +508,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
|
|
||||||
def get_pids_of(self, process_name):
|
def get_pids_of(self, process_name):
|
||||||
"""Returns a list of PIDs of all processes with the specified name."""
|
"""Returns a list of PIDs of all processes with the specified name."""
|
||||||
result = self.execute('ps | {} grep {}'.format(self.busybox, process_name),
|
result = (self.execute('ps | {} grep {}'.format(self.busybox, process_name),
|
||||||
check_exit_code=False).strip()
|
check_exit_code=False) or '').strip()
|
||||||
if result and 'not found' not in result:
|
if result and 'not found' not in result:
|
||||||
return [int(x.split()[1]) for x in result.split('\n')]
|
return [int(x.split()[1]) for x in result.split('\n')]
|
||||||
else:
|
else:
|
||||||
@@ -509,11 +550,10 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
props['android_id'] = self.get_android_id()
|
props['android_id'] = self.get_android_id()
|
||||||
self._update_build_properties(props)
|
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')
|
dumpsys_host_file = os.path.join(context.host_working_directory, 'window.dumpsys')
|
||||||
self.execute('{} > {}'.format('dumpsys window', dumpsys_target_file))
|
with open(dumpsys_host_file, 'w') as wfh:
|
||||||
self.pull_file(dumpsys_target_file, dumpsys_host_file)
|
wfh.write(self.execute('dumpsys window'))
|
||||||
context.add_run_artifact('dumpsys_window', dumpsys_host_file, 'meta')
|
context.add_run_artifact('dumpsys_window', dumpsys_host_file, 'meta')
|
||||||
|
|
||||||
prop_file = os.path.join(context.host_working_directory, 'android-props.json')
|
prop_file = os.path.join(context.host_working_directory, 'android-props.json')
|
||||||
with open(prop_file, 'w') as wfh:
|
with open(prop_file, 'w') as wfh:
|
||||||
@@ -611,6 +651,17 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
self.pull_file(on_device_file, filepath)
|
self.pull_file(on_device_file, filepath)
|
||||||
self.delete_file(on_device_file)
|
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):
|
def is_screen_on(self):
|
||||||
"""Returns ``True`` if the device screen is currently on, ``False`` otherwise."""
|
"""Returns ``True`` if the device screen is currently on, ``False`` otherwise."""
|
||||||
output = self.execute('dumpsys power')
|
output = self.execute('dumpsys power')
|
||||||
@@ -665,16 +716,40 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
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 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):
|
def _update_build_properties(self, props):
|
||||||
try:
|
try:
|
||||||
def strip(somestring):
|
regex = re.compile(r'\[([^\]]+)\]\s*:\s*\[([^\]]+)\]')
|
||||||
return somestring.strip().replace('[', '').replace(']', '')
|
for match in regex.finditer(self.execute("getprop")):
|
||||||
for line in self.execute("getprop").splitlines():
|
key = match.group(1).strip()
|
||||||
key, value = line.split(':', 1)
|
value = match.group(2).strip()
|
||||||
key = strip(key)
|
|
||||||
value = strip(value)
|
|
||||||
props[key] = value
|
props[key] = value
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.logger.warning('Could not parse build.prop.')
|
self.logger.warning('Could not parse build.prop.')
|
||||||
|
@@ -34,3 +34,13 @@ class JarFile(FileResource):
|
|||||||
class ApkFile(FileResource):
|
class ApkFile(FileResource):
|
||||||
|
|
||||||
name = 'apk'
|
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.
577
wlauto/common/android/workload.py
Normal file → Executable file
577
wlauto/common/android/workload.py
Normal file → Executable file
@@ -17,27 +17,37 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import time
|
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.workload import Workload
|
||||||
from wlauto.core.resource import NO_ONE
|
from wlauto.common.android.resources import ApkFile
|
||||||
from wlauto.common.resources import ExtensionAsset, Executable
|
from wlauto.common.resources import ExtensionAsset, File
|
||||||
from wlauto.exceptions import WorkloadError, ResourceError, ConfigError
|
from wlauto.exceptions import WorkloadError, ResourceError, DeviceError
|
||||||
from wlauto.utils.android import ApkInfo, ANDROID_NORMAL_PERMISSIONS
|
from wlauto.utils.android import (ApkInfo, ANDROID_NORMAL_PERMISSIONS,
|
||||||
from wlauto.utils.types import boolean
|
ANDROID_UNCHANGEABLE_PERMISSIONS, UNSUPPORTED_PACKAGES)
|
||||||
import wlauto.common.android.resources
|
from wlauto.utils.types import boolean, ParameterDict
|
||||||
|
import wlauto.utils.statedetect as state_detector
|
||||||
|
from wlauto.common.linux.workload import ReventWorkload
|
||||||
|
|
||||||
|
|
||||||
DELAY = 5
|
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):
|
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
|
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
|
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 JAR file in
|
and invoking it to run the workload. By default, it will look for the ``*.apk`` file
|
||||||
the same directory as the .py file for the workload (this can be changed by overriding
|
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).
|
the ``uiauto_file`` property in the subclassing workload).
|
||||||
|
|
||||||
To inintiate UI Automation, the fully-qualified name of the Java class and the
|
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
|
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.
|
``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.
|
``self.uiauto_params`` dict inside your class's ``__init__`` or ``setup`` methods.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -58,38 +68,43 @@ class UiAutomatorWorkload(Workload):
|
|||||||
|
|
||||||
uiauto_package = ''
|
uiauto_package = ''
|
||||||
uiauto_class = 'UiAutomation'
|
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
|
# Can be overidden by subclasses to adjust to run time of specific
|
||||||
# benchmarks.
|
# 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
|
def __init__(self, device, _call_super=True, **kwargs): # pylint: disable=W0613
|
||||||
if _call_super:
|
if _call_super:
|
||||||
super(UiAutomatorWorkload, self).__init__(device, **kwargs)
|
Workload.__init__(self, device, **kwargs)
|
||||||
self.uiauto_file = None
|
self.uiauto_file = None
|
||||||
self.device_uiauto_file = None
|
|
||||||
self.command = None
|
self.command = None
|
||||||
self.uiauto_params = {}
|
self.uiauto_params = ParameterDict()
|
||||||
|
|
||||||
def init_resources(self, context):
|
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:
|
if not self.uiauto_file:
|
||||||
raise ResourceError('No UI automation JAR file found for workload {}.'.format(self.name))
|
raise ResourceError('No UI automation APK 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))
|
|
||||||
if not self.uiauto_package:
|
if not self.uiauto_package:
|
||||||
self.uiauto_package = os.path.splitext(os.path.basename(self.uiauto_file))[0]
|
self.uiauto_package = os.path.splitext(os.path.basename(self.uiauto_file))[0]
|
||||||
|
|
||||||
def setup(self, context):
|
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 = self.uiauto_params
|
||||||
params_dict['workdir'] = self.device.working_directory
|
params_dict['workdir'] = self.device.working_directory
|
||||||
params = ''
|
params = ''
|
||||||
for k, v in self.uiauto_params.iteritems():
|
for k, v in self.uiauto_params.iter_encoded_items():
|
||||||
params += ' -e {} "{}"'.format(k, v)
|
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)
|
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')
|
self.device.killall('uiautomator')
|
||||||
|
|
||||||
def run(self, context):
|
def run(self, context):
|
||||||
@@ -104,11 +119,12 @@ class UiAutomatorWorkload(Workload):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def teardown(self, context):
|
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):
|
def validate(self):
|
||||||
if not self.uiauto_file:
|
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:
|
if not self.uiauto_package:
|
||||||
raise WorkloadError('No UI automation package specified for workload {}.'.format(self.name))
|
raise WorkloadError('No UI automation package specified for workload {}.'.format(self.name))
|
||||||
|
|
||||||
@@ -127,6 +143,11 @@ class ApkWorkload(Workload):
|
|||||||
:view: The class of the main view pane of the app. This needs to be defined in order
|
: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
|
to collect SurfaceFlinger-derived statistics (such as FPS) for the app, but
|
||||||
may otherwise be left as ``None``.
|
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
|
: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
|
the size and nature of a specific APK, and so should be defined on
|
||||||
per-workload basis.
|
per-workload basis.
|
||||||
@@ -136,6 +157,9 @@ class ApkWorkload(Workload):
|
|||||||
so, as with all timeouts, so leeway must be included in
|
so, as with all timeouts, so leeway must be included in
|
||||||
the specified value.
|
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
|
.. 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.
|
the ``aapt`` tool that comes with the ADT (Android Developemnt Tools) bundle.
|
||||||
|
|
||||||
@@ -143,95 +167,235 @@ class ApkWorkload(Workload):
|
|||||||
package = None
|
package = None
|
||||||
activity = None
|
activity = None
|
||||||
view = None
|
view = None
|
||||||
|
min_apk_version = None
|
||||||
|
max_apk_version = None
|
||||||
supported_platforms = ['android']
|
supported_platforms = ['android']
|
||||||
|
launch_main = True
|
||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
Parameter('install_timeout', kind=int, default=300,
|
Parameter('install_timeout', kind=int, default=300,
|
||||||
description='Timeout for the installation of the apk.'),
|
description='Timeout for the installation of the apk.'),
|
||||||
Parameter('check_apk', kind=boolean, default=True,
|
Parameter('check_apk', kind=boolean, default=True,
|
||||||
description='''
|
description='''
|
||||||
Discover the APK for this workload on the host, and check that
|
When set to True the APK file on the host will be prefered if
|
||||||
the version matches the one on device (if already installed).
|
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,
|
Parameter('force_install', kind=boolean, default=False,
|
||||||
description='''
|
description='''
|
||||||
Always re-install the APK, even if matching version is found
|
Always re-install the APK, even if matching version is found already installed
|
||||||
on already installed on the device.
|
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,
|
Parameter('uninstall_apk', kind=boolean, default=False,
|
||||||
description='If ``True``, will uninstall workload\'s APK as part of teardown.'),
|
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):
|
def __init__(self, device, _call_super=True, **kwargs):
|
||||||
if _call_super:
|
if _call_super:
|
||||||
super(ApkWorkload, self).__init__(device, **kwargs)
|
Workload.__init__(self, device, **kwargs)
|
||||||
self.apk_file = None
|
self.apk_file = None
|
||||||
self.apk_version = None
|
self.apk_version = None
|
||||||
self.logcat_log = None
|
self.logcat_log = None
|
||||||
|
self.exact_apk_version = None
|
||||||
|
|
||||||
def init_resources(self, context):
|
def setup(self, context): # pylint: disable=too-many-branches
|
||||||
self.apk_file = context.resolver.get(wlauto.common.android.resources.ApkFile(self),
|
Workload.setup(self, context)
|
||||||
version=getattr(self, 'version', None),
|
self.setup_workload_apk(context)
|
||||||
strict=self.check_apk)
|
self.launch_application()
|
||||||
|
self.kill_background()
|
||||||
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.launch_package()
|
|
||||||
self.device.execute('am kill-all') # kill all *background* activities
|
|
||||||
self.device.clear_logcat()
|
self.device.clear_logcat()
|
||||||
|
|
||||||
def initialize_package(self, context):
|
def setup_workload_apk(self, context):
|
||||||
installed_version = self.device.get_installed_package_version(self.package)
|
# Get target version
|
||||||
if self.check_apk:
|
target_version = self.device.get_installed_package_version(self.package)
|
||||||
self.initialize_with_host_apk(context, installed_version)
|
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)
|
||||||
|
|
||||||
|
if self.force_install:
|
||||||
|
self.force_install_apk(context, host_version)
|
||||||
|
elif self.check_apk:
|
||||||
|
self.prefer_host_apk(context, host_version, target_version)
|
||||||
else:
|
else:
|
||||||
if not installed_version:
|
self.prefer_target_apk(context, host_version, target_version)
|
||||||
message = '''{} not found found on the device and check_apk is set to "False"
|
|
||||||
so host version was not checked.'''
|
self.reset(context)
|
||||||
raise WorkloadError(message.format(self.package))
|
self.apk_version = self.device.get_installed_package_version(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
|
|
||||||
context.add_classifiers(apk_version=self.apk_version)
|
context.add_classifiers(apk_version=self.apk_version)
|
||||||
|
|
||||||
def initialize_with_host_apk(self, context, installed_version):
|
def check_host_version(self):
|
||||||
host_version = ApkInfo(self.apk_file).version_name
|
host_version = None
|
||||||
if installed_version != host_version:
|
if self.apk_file is not None:
|
||||||
if installed_version:
|
host_version = ApkInfo(self.apk_file).version_name
|
||||||
message = '{} host version: {}, device version: {}; re-installing...'
|
if host_version:
|
||||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
host_version = LooseVersion(host_version)
|
||||||
host_version, installed_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:
|
else:
|
||||||
message = '{} host version: {}, not found on device; installing...'
|
msg += "but the APK was not found on the host, using target version"
|
||||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
self.logger.debug(msg)
|
||||||
host_version))
|
return
|
||||||
self.force_install = True # pylint: disable=attribute-defined-outside-init
|
try:
|
||||||
else:
|
self.validate_version(host_version)
|
||||||
message = '{} version {} found on both device and host.'
|
except ResourceError as e1:
|
||||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
msg += "but the host APK version is invalid:\n\t{}\n"
|
||||||
host_version))
|
if target_version is None:
|
||||||
if self.force_install:
|
msg += "The target does not have the app either"
|
||||||
if installed_version:
|
raise ResourceError(msg.format(str(e1)))
|
||||||
self.device.uninstall(self.package)
|
try:
|
||||||
# It's possible that that the uninstall above fails, which will result in
|
self.validate_version(target_version)
|
||||||
# install failing and a warning, hower execution would the proceed, so need
|
except ResourceError as e2:
|
||||||
# to make sure that the right apk_vesion is reported in the end.
|
msg += "The target version is also invalid:\n\t{}"
|
||||||
if self.install_apk(context):
|
raise ResourceError(msg.format(str(e1), str(e2)))
|
||||||
self.apk_version = host_version
|
|
||||||
else:
|
else:
|
||||||
self.apk_version = installed_version
|
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:
|
else:
|
||||||
self.apk_version = installed_version
|
msg += "and a valid version of the app is already on the target, using target app"
|
||||||
self.reset(context)
|
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):
|
def launch_package(self):
|
||||||
if not self.activity:
|
if not self.activity:
|
||||||
@@ -245,16 +409,21 @@ class ApkWorkload(Workload):
|
|||||||
|
|
||||||
def reset(self, context): # pylint: disable=W0613
|
def reset(self, context): # pylint: disable=W0613
|
||||||
self.device.execute('am force-stop {}'.format(self.package))
|
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,
|
# 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 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:
|
if self.device.get_sdk_version() >= 23:
|
||||||
self._grant_requested_permissions()
|
self._grant_requested_permissions()
|
||||||
|
|
||||||
def install_apk(self, context):
|
def install_apk(self, context, replace=False):
|
||||||
success = False
|
success = False
|
||||||
output = self.device.install(self.apk_file, self.install_timeout)
|
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 'Failure' in output:
|
||||||
if 'ALREADY_EXISTS' in output:
|
if 'ALREADY_EXISTS' in output:
|
||||||
self.logger.warn('Using already installed APK (did not unistall properly?)')
|
self.logger.warn('Using already installed APK (did not unistall properly?)')
|
||||||
@@ -278,17 +447,29 @@ class ApkWorkload(Workload):
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
if "android.permission." in line:
|
if "android.permission." in line:
|
||||||
permissions.append(line.split(":")[0].strip())
|
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
|
break
|
||||||
|
|
||||||
for permission in permissions:
|
for permission in set(permissions):
|
||||||
# "Normal" Permisions are automatically granted and cannot be changed
|
# "Normal" Permisions are automatically granted and cannot be changed
|
||||||
permission_name = permission.rsplit('.', 1)[1]
|
permission_name = permission.rsplit('.', 1)[1]
|
||||||
if permission_name not in ANDROID_NORMAL_PERMISSIONS:
|
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):
|
def do_post_install(self, context):
|
||||||
""" May be overwritten by dervied classes."""
|
""" May be overwritten by derived classes."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self, context):
|
def run(self, context):
|
||||||
@@ -307,77 +488,8 @@ class ApkWorkload(Workload):
|
|||||||
if self.uninstall_apk:
|
if self.uninstall_apk:
|
||||||
self.device.uninstall(self.package)
|
self.device.uninstall(self.package)
|
||||||
|
|
||||||
|
|
||||||
AndroidBenchmark = ApkWorkload # backward compatibility
|
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.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
|
|
||||||
self.on_device_setup_revent = None
|
|
||||||
self.on_device_run_revent = None
|
|
||||||
|
|
||||||
def initialize(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'))
|
|
||||||
devpath = self.device.path
|
|
||||||
self.on_device_setup_revent = devpath.join(self.device.working_directory,
|
|
||||||
os.path.split(self.revent_setup_file)[-1])
|
|
||||||
self.on_device_run_revent = devpath.join(self.device.working_directory,
|
|
||||||
os.path.split(self.revent_run_file)[-1])
|
|
||||||
self._check_revent_files(context)
|
|
||||||
|
|
||||||
def setup(self, 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.killall('revent')
|
|
||||||
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):
|
class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
||||||
|
|
||||||
supported_platforms = ['android']
|
supported_platforms = ['android']
|
||||||
@@ -386,6 +498,11 @@ class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
|||||||
UiAutomatorWorkload.__init__(self, device, **kwargs)
|
UiAutomatorWorkload.__init__(self, device, **kwargs)
|
||||||
AndroidBenchmark.__init__(self, device, _call_super=False, **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):
|
def init_resources(self, context):
|
||||||
UiAutomatorWorkload.init_resources(self, context)
|
UiAutomatorWorkload.init_resources(self, context)
|
||||||
AndroidBenchmark.init_resources(self, context)
|
AndroidBenchmark.init_resources(self, context)
|
||||||
@@ -402,6 +519,98 @@ class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
|||||||
UiAutomatorWorkload.teardown(self, context)
|
UiAutomatorWorkload.teardown(self, context)
|
||||||
AndroidBenchmark.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):
|
class GameWorkload(ApkWorkload, ReventWorkload):
|
||||||
"""
|
"""
|
||||||
@@ -435,21 +644,22 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
|||||||
view = 'SurfaceView'
|
view = 'SurfaceView'
|
||||||
loading_time = 10
|
loading_time = 10
|
||||||
supported_platforms = ['android']
|
supported_platforms = ['android']
|
||||||
|
setup_required = True
|
||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
Parameter('install_timeout', default=500, override=True),
|
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,
|
Parameter('assets_push_timeout', kind=int, default=500,
|
||||||
description='Timeout used during deployment of the assets package (if there is one).'),
|
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
|
def __init__(self, device, **kwargs): # pylint: disable=W0613
|
||||||
ApkWorkload.__init__(self, device, **kwargs)
|
ApkWorkload.__init__(self, device, **kwargs)
|
||||||
ReventWorkload.__init__(self, device, _call_super=False, **kwargs)
|
ReventWorkload.__init__(self, device, _call_super=False, **kwargs)
|
||||||
|
if self.check_states:
|
||||||
|
state_detector.check_match_state_dependencies()
|
||||||
self.logcat_process = None
|
self.logcat_process = None
|
||||||
self.module_dir = os.path.dirname(sys.modules[self.__module__].__file__)
|
self.module_dir = os.path.dirname(sys.modules[self.__module__].__file__)
|
||||||
self.revent_dir = os.path.join(self.module_dir, 'revent_files')
|
self.revent_dir = os.path.join(self.module_dir, 'revent_files')
|
||||||
@@ -457,6 +667,8 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
|||||||
def init_resources(self, context):
|
def init_resources(self, context):
|
||||||
ApkWorkload.init_resources(self, context)
|
ApkWorkload.init_resources(self, context)
|
||||||
ReventWorkload.init_resources(self, context)
|
ReventWorkload.init_resources(self, context)
|
||||||
|
if self.check_states:
|
||||||
|
self._check_statedetection_files(context)
|
||||||
|
|
||||||
def setup(self, context):
|
def setup(self, context):
|
||||||
ApkWorkload.setup(self, context)
|
ApkWorkload.setup(self, context)
|
||||||
@@ -464,6 +676,10 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
|||||||
time.sleep(self.loading_time)
|
time.sleep(self.loading_time)
|
||||||
ReventWorkload.setup(self, context)
|
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):
|
def do_post_install(self, context):
|
||||||
ApkWorkload.do_post_install(self, context)
|
ApkWorkload.do_post_install(self, context)
|
||||||
self._deploy_assets(context, self.assets_push_timeout)
|
self._deploy_assets(context, self.assets_push_timeout)
|
||||||
@@ -483,6 +699,10 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
|||||||
ReventWorkload.run(self, context)
|
ReventWorkload.run(self, context)
|
||||||
|
|
||||||
def teardown(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:
|
if not self.saved_state_file:
|
||||||
ApkWorkload.teardown(self, context)
|
ApkWorkload.teardown(self, context)
|
||||||
else:
|
else:
|
||||||
@@ -514,3 +734,22 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
|||||||
self.device.busybox,
|
self.device.busybox,
|
||||||
ondevice_cache)
|
ondevice_cache)
|
||||||
self.device.execute(deploy_command, timeout=timeout, as_root=True)
|
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.
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# 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.
|
# gem5 might be slow. Hence, we need to make the ping timeout very long.
|
||||||
def ping(self):
|
def ping(self):
|
||||||
self.logger.debug("Pinging gem5 to see if it is still alive")
|
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.
|
# Additional Android-specific methods.
|
||||||
def forward_port(self, _): # pylint: disable=R0201
|
def forward_port(self, _): # pylint: disable=R0201
|
||||||
|
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import base64
|
||||||
import socket
|
import socket
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from subprocess import CalledProcessError
|
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')
|
PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
|
||||||
LsmodEntry = namedtuple('LsmodEntry', ['name', 'size', 'use_count', 'used_by'])
|
LsmodEntry = namedtuple('LsmodEntry', ['name', 'size', 'use_count', 'used_by'])
|
||||||
|
|
||||||
|
GOOGLE_DNS_SERVER_ADDRESS = '8.8.8.8'
|
||||||
|
|
||||||
|
|
||||||
class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||||
|
|
||||||
@@ -92,12 +95,18 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
'''),
|
'''),
|
||||||
Parameter('binaries_directory',
|
Parameter('binaries_directory',
|
||||||
description='Location of executable binaries on this device (must be in PATH).'),
|
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 = [
|
runtime_parameters = [
|
||||||
RuntimeParameter('sysfile_values', 'get_sysfile_values', 'set_sysfile_values', value_name='params'),
|
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'),
|
value_name='number'),
|
||||||
CoreParameter('${core}_min_frequency', 'get_core_min_frequency', 'set_core_min_frequency',
|
CoreParameter('${core}_min_frequency', 'get_core_min_frequency', 'set_core_min_frequency',
|
||||||
value_name='freq'),
|
value_name='freq'),
|
||||||
@@ -128,6 +137,10 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
self._abi = val
|
self._abi = val
|
||||||
return self._abi
|
return self._abi
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_abi(self):
|
||||||
|
return [self.abi]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_cpus(self):
|
def online_cpus(self):
|
||||||
val = self.get_sysfile_value('/sys/devices/system/cpu/online')
|
val = self.get_sysfile_value('/sys/devices/system/cpu/online')
|
||||||
@@ -229,7 +242,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
self.logger.debug('Could not pull property file "{}"'.format(propfile))
|
self.logger.debug('Could not pull property file "{}"'.format(propfile))
|
||||||
return {}
|
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.
|
Get the contents of the specified sysfile.
|
||||||
|
|
||||||
@@ -239,28 +252,49 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
be any Python callable that takes a single str argument.
|
be any Python callable that takes a single str argument.
|
||||||
If not specified or is None, the contents will be returned
|
If not specified or is None, the contents will be returned
|
||||||
as a string.
|
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:
|
if kind:
|
||||||
return kind(output)
|
return kind(output)
|
||||||
else:
|
else:
|
||||||
return output
|
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.
|
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)
|
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:
|
if verify:
|
||||||
output = self.get_sysfile_value(sysfile)
|
output = self.get_sysfile_value(sysfile, binary=binary)
|
||||||
if output.strip() != value: # pylint: disable=E1103
|
if output.strip() != value: # pylint: disable=E1103
|
||||||
message = 'Could not set the value of {} to {}'.format(sysfile, value)
|
message = 'Could not set the value of {} to {}'.format(sysfile, value)
|
||||||
raise DeviceError(message)
|
raise DeviceError(message)
|
||||||
self._written_sysfiles.append(sysfile)
|
self._written_sysfiles.append((sysfile, binary))
|
||||||
|
|
||||||
def get_sysfile_values(self):
|
def get_sysfile_values(self):
|
||||||
"""
|
"""
|
||||||
@@ -269,21 +303,24 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
values = {}
|
values = {}
|
||||||
for sysfile in self._written_sysfiles:
|
for sysfile, binary in self._written_sysfiles:
|
||||||
values[sysfile] = self.get_sysfile_value(sysfile)
|
values[sysfile] = self.get_sysfile_value(sysfile, binary=binary)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def set_sysfile_values(self, params):
|
def set_sysfile_values(self, params):
|
||||||
"""
|
"""
|
||||||
The plural version of ``set_sysfile_value``. Takes a single parameter which is a mapping of
|
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
|
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.
|
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():
|
for sysfile, value in params.iteritems():
|
||||||
verify = not sysfile.endswith('!')
|
verify = not sysfile.endswith('!')
|
||||||
sysfile = sysfile.rstrip('!')
|
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):
|
def deploy_busybox(self, context, force=False):
|
||||||
"""
|
"""
|
||||||
@@ -312,6 +349,29 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
|
|
||||||
please see: https://pythonhosted.org/wlauto/writing_extensions.html""")
|
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):
|
def get_binary_path(self, name, search_system_binaries=True):
|
||||||
"""
|
"""
|
||||||
Searches the devices ``binary_directory`` for the given binary,
|
Searches the devices ``binary_directory`` for the given binary,
|
||||||
@@ -417,20 +477,6 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
else:
|
else:
|
||||||
raise ValueError(c)
|
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
|
# hotplug
|
||||||
|
|
||||||
def enable_cpu(self, cpu):
|
def enable_cpu(self, cpu):
|
||||||
@@ -469,17 +515,17 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
sysfile = '/sys/devices/system/cpu/{}/online'.format(cpu)
|
sysfile = '/sys/devices/system/cpu/{}/online'.format(cpu)
|
||||||
self.set_sysfile_value(sysfile, status)
|
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:
|
if core not in self.core_names:
|
||||||
raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(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
|
num_active_cores = 0
|
||||||
for i, c in enumerate(self.core_names):
|
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
|
num_active_cores += 1
|
||||||
return num_active_cores
|
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:
|
if core not in self.core_names:
|
||||||
raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(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]
|
core_ids = [i for i, c in enumerate(self.core_names) if c == core]
|
||||||
@@ -492,8 +538,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
# make sure at least one other core is enabled to avoid trying to
|
# make sure at least one other core is enabled to avoid trying to
|
||||||
# hotplug everything.
|
# hotplug everything.
|
||||||
for i, c in enumerate(self.core_names):
|
for i, c in enumerate(self.core_names):
|
||||||
if c != core:
|
if c != core and i in self.online_cpus:
|
||||||
self.enable_cpu(i)
|
|
||||||
break
|
break
|
||||||
else: # did not find one
|
else: # did not find one
|
||||||
raise ValueError('Cannot hotplug all cpus on the device!')
|
raise ValueError('Cannot hotplug all cpus on the device!')
|
||||||
@@ -545,7 +590,8 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
def get_device_model(self):
|
def get_device_model(self):
|
||||||
if self.file_exists("/proc/device-tree/model"):
|
if self.file_exists("/proc/device-tree/model"):
|
||||||
raw_model = self.execute("cat /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
|
# Right now we don't know any other way to get device model
|
||||||
# info in linux on arm platforms
|
# info in linux on arm platforms
|
||||||
return None
|
return None
|
||||||
@@ -554,7 +600,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
|
|
||||||
def _check_ready(self):
|
def _check_ready(self):
|
||||||
if not self._is_ready:
|
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):
|
def _get_core_cluster(self, core):
|
||||||
"""Returns the first cluster that has cores of the specified type. Raises
|
"""Returns the first cluster that has cores of the specified type. Raises
|
||||||
@@ -588,17 +634,11 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
description='Optionally, telnet may be used instead of ssh, though this is discouraged.'),
|
description='Optionally, telnet may be used instead of ssh, though this is discouraged.'),
|
||||||
Parameter('boot_timeout', kind=int, default=120,
|
Parameter('boot_timeout', kind=int, default=120,
|
||||||
description='How long to try to connect to the device after a reboot.'),
|
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
|
@property
|
||||||
def is_rooted(self):
|
def is_rooted(self):
|
||||||
|
self._check_ready()
|
||||||
if self._is_rooted is None:
|
if self._is_rooted is None:
|
||||||
# First check if the user is root
|
# First check if the user is root
|
||||||
try:
|
try:
|
||||||
|
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
|
# How many times a job will be re-run before giving up
|
||||||
max_retries = 3
|
max_retries = 3
|
||||||
|
|
||||||
|
# If WA should delete its files from the device after the run is completed
|
||||||
|
clean_up = False
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
######################################### Device Settings ##########################################
|
######################################### Device Settings ##########################################
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Specify the device you want to run workload automation on. This must be a #
|
# 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'
|
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, #
|
# 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 #
|
# which in turn determines what additional data (such as /proc/interrupts content or Streamline #
|
||||||
@@ -189,7 +193,7 @@ logging = {
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
#################################### Instruments Configuration #####################################
|
#################################### 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 #
|
# instrumentations require specific settings in order for them to work. These settings are #
|
||||||
# specified here. #
|
# specified here. #
|
||||||
# Note that these settings only take effect if the corresponding instrument is
|
# Note that these settings only take effect if the corresponding instrument is
|
||||||
@@ -222,10 +226,10 @@ logging = {
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
######################################### DAQ configuration ########################################
|
######################################### 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'
|
#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
|
#daq_server_port = 56788
|
||||||
|
|
||||||
# The values of resistors 1 and 2 (in Ohms) across which the voltages are measured
|
# 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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -152,7 +152,8 @@ def init_environment(env_root, dep_dir, extension_paths, overwrite_existing=Fals
|
|||||||
os.makedirs(env_root)
|
os.makedirs(env_root)
|
||||||
with open(os.path.join(_this_dir, '..', 'config_example.py')) as rf:
|
with open(os.path.join(_this_dir, '..', 'config_example.py')) as rf:
|
||||||
text = re.sub(r'""".*?"""', '', rf.read(), 1, re.DOTALL)
|
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)
|
wf.write(text)
|
||||||
|
|
||||||
os.makedirs(dep_dir)
|
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)
|
os.chown(os.path.join(root, d), uid, gid)
|
||||||
for f in files: # pylint: disable=W0621
|
for f in files: # pylint: disable=W0621
|
||||||
os.chown(os.path.join(root, f), uid, gid)
|
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.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')
|
_dep_dir = os.path.join(_env_root, 'dependencies')
|
||||||
_extension_paths = [os.path.join(_env_root, ext.default_path) for ext in _extensions]
|
_extension_paths = [os.path.join(_env_root, ext.default_path) for ext in _extensions]
|
||||||
_env_var_paths = os.getenv('WA_EXTENSION_PATHS', '')
|
_env_var_paths = os.getenv('WA_EXTENSION_PATHS', '')
|
||||||
@@ -189,7 +192,8 @@ for filename in ['config.py', 'config.yaml']:
|
|||||||
_env_configs.append(filepath)
|
_env_configs.append(filepath)
|
||||||
|
|
||||||
if not os.path.isdir(_env_root):
|
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:
|
elif not _env_configs:
|
||||||
filepath = os.path.join(_env_root, 'config.py')
|
filepath = os.path.join(_env_root, 'config.py')
|
||||||
with open(os.path.join(_this_dir, '..', 'config_example.py')) as f:
|
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:
|
for config in _env_configs:
|
||||||
settings.update(config)
|
settings.update(config)
|
||||||
|
|
||||||
|
@@ -481,6 +481,7 @@ class RunConfiguration(object):
|
|||||||
RunConfigurationItem('flashing_config', 'dict', 'replace'),
|
RunConfigurationItem('flashing_config', 'dict', 'replace'),
|
||||||
RunConfigurationItem('retry_on_status', 'list', 'replace'),
|
RunConfigurationItem('retry_on_status', 'list', 'replace'),
|
||||||
RunConfigurationItem('max_retries', 'scalar', 'replace'),
|
RunConfigurationItem('max_retries', 'scalar', 'replace'),
|
||||||
|
RunConfigurationItem('clean_up', 'scalar', 'replace'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Configuration specified for each workload spec. "workload_parameters"
|
# Configuration specified for each workload spec. "workload_parameters"
|
||||||
@@ -757,7 +758,7 @@ class RunConfiguration(object):
|
|||||||
if spec.match_selectors(selectors):
|
if spec.match_selectors(selectors):
|
||||||
instrumentation_config = self._raw_config['instrumentation']
|
instrumentation_config = self._raw_config['instrumentation']
|
||||||
for instname in spec.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)
|
instrumentation_config.append(instname)
|
||||||
self.workload_specs.append(spec)
|
self.workload_specs.append(spec)
|
||||||
|
|
||||||
|
@@ -381,7 +381,20 @@ class Device(Extension):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
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
|
Write the specified value to the specified file on the device
|
||||||
and verify that the value has actually been written.
|
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
|
:param value: The value to be written to the file. Must be
|
||||||
an int or a string convertable to an int.
|
an int or a string convertable to an int.
|
||||||
:param verify: Specifies whether the value should be verified, once written.
|
: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.
|
Should raise DeviceError if could write value.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
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.
|
Get the contents of the specified sysfile.
|
||||||
|
|
||||||
@@ -406,6 +420,8 @@ class Device(Extension):
|
|||||||
be any Python callable that takes a single str argument.
|
be any Python callable that takes a single str argument.
|
||||||
If not specified or is None, the contents will be returned
|
If not specified or is None, the contents will be returned
|
||||||
as a string.
|
as a string.
|
||||||
|
:param binary: Whether the value should be encoded into base64 for reading
|
||||||
|
to deal with binary format.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -426,6 +442,13 @@ class Device(Extension):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def is_network_connected(self):
|
||||||
|
"""
|
||||||
|
Checks if the device is connected to the internet
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Device<{}>'.format(self.name)
|
return 'Device<{}>'.format(self.name)
|
||||||
|
|
||||||
|
@@ -14,10 +14,12 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@@ -41,6 +43,9 @@ def load_commands(subparsers):
|
|||||||
for command in ext_loader.list_commands():
|
for command in ext_loader.list_commands():
|
||||||
settings.commands[command.name] = ext_loader.get_command(command.name, subparsers=subparsers)
|
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():
|
def main():
|
||||||
try:
|
try:
|
||||||
@@ -62,6 +67,7 @@ def main():
|
|||||||
settings.update(args.config)
|
settings.update(args.config)
|
||||||
init_logging(settings.verbosity)
|
init_logging(settings.verbosity)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGTERM, convert_TERM_into_INT_handler)
|
||||||
command = settings.commands[args.command]
|
command = settings.commands[args.command]
|
||||||
sys.exit(command.execute(args))
|
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.resolver import ResourceResolver
|
||||||
from wlauto.core.result import ResultManager, IterationResult, RunResult
|
from wlauto.core.result import ResultManager, IterationResult, RunResult
|
||||||
from wlauto.exceptions import (WAError, ConfigError, TimeoutError, InstrumentError,
|
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
|
from wlauto.utils.misc import ensure_directory_exists as _d, get_traceback, merge_dicts, format_duration
|
||||||
|
|
||||||
|
|
||||||
@@ -344,6 +345,11 @@ class Executor(object):
|
|||||||
runner = self._get_runner(result_manager)
|
runner = self._get_runner(result_manager)
|
||||||
runner.init_queue(self.config.workload_specs)
|
runner.init_queue(self.config.workload_specs)
|
||||||
runner.run()
|
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()
|
self.execute_postamble()
|
||||||
|
|
||||||
def execute_postamble(self):
|
def execute_postamble(self):
|
||||||
@@ -636,7 +642,7 @@ class Runner(object):
|
|||||||
job.iteration = self.context.current_iteration
|
job.iteration = self.context.current_iteration
|
||||||
if job.result.status in self.config.retry_on_status:
|
if job.result.status in self.config.retry_on_status:
|
||||||
if job.retry >= self.config.max_retries:
|
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:
|
else:
|
||||||
self.logger.info('Job status was {}. Retrying...'.format(job.result.status))
|
self.logger.info('Job status was {}. Retrying...'.format(job.result.status))
|
||||||
retry_job = RunnerJob(job.spec, job.retry + 1)
|
retry_job = RunnerJob(job.spec, job.retry + 1)
|
||||||
@@ -731,6 +737,13 @@ class Runner(object):
|
|||||||
filepath = os.path.join(settings.output_directory, filename)
|
filepath = os.path.join(settings.output_directory, filename)
|
||||||
self.device.capture_screen(filepath)
|
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
|
@contextmanager
|
||||||
def _handle_errors(self, action, on_error_status=IterationResult.FAILED):
|
def _handle_errors(self, action, on_error_status=IterationResult.FAILED):
|
||||||
try:
|
try:
|
||||||
@@ -744,15 +757,21 @@ class Runner(object):
|
|||||||
if self.current_job:
|
if self.current_job:
|
||||||
self.current_job.result.status = on_error_status
|
self.current_job.result.status = on_error_status
|
||||||
self.current_job.result.add_event(str(we))
|
self.current_job.result.add_event(str(we))
|
||||||
try:
|
|
||||||
self._take_screenshot('error.png')
|
# There is no point in taking a screenshot ect if the issue is not
|
||||||
except Exception, e: # pylint: disable=W0703
|
# with the device but with the host or a missing resource
|
||||||
# We're already in error state, so the fact that taking a
|
if not (isinstance(we, ResourceError) or isinstance(we, HostError)):
|
||||||
# screenshot failed is not surprising...
|
try:
|
||||||
pass
|
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:
|
if action:
|
||||||
action = action[0].lower() + action[1:]
|
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
|
except Exception, e: # pylint: disable=W0703
|
||||||
error_text = '{}("{}")'.format(e.__class__.__name__, e)
|
error_text = '{}("{}")'.format(e.__class__.__name__, e)
|
||||||
if self.current_job:
|
if self.current_job:
|
||||||
|
@@ -224,18 +224,11 @@ class Param(object):
|
|||||||
else:
|
else:
|
||||||
new_value = current_value + [value]
|
new_value = current_value + [value]
|
||||||
setattr(obj, self.name, new_value)
|
setattr(obj, self.name, new_value)
|
||||||
|
|
||||||
def validate(self, obj):
|
|
||||||
value = getattr(obj, self.name, None)
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if self.allowed_values:
|
if self.allowed_values:
|
||||||
self._validate_allowed_values(obj, value)
|
self._validate_allowed_values(obj, value)
|
||||||
if self.constraint:
|
if self.constraint:
|
||||||
self._validate_constraint(obj, value)
|
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):
|
def get_type_name(self):
|
||||||
typename = str(self.kind)
|
typename = str(self.kind)
|
||||||
@@ -567,7 +560,9 @@ class Extension(object):
|
|||||||
if self.name is None:
|
if self.name is None:
|
||||||
raise ValidationError('Name not set for {}'.format(self._classname))
|
raise ValidationError('Name not set for {}'.format(self._classname))
|
||||||
for param in self.parameters:
|
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):
|
def initialize(self, context):
|
||||||
pass
|
pass
|
||||||
|
@@ -32,4 +32,3 @@ def get_extension_type(ext):
|
|||||||
if isinstance(ext, cls):
|
if isinstance(ext, cls):
|
||||||
return name
|
return name
|
||||||
raise ValueError('Unknown extension type: {}'.format(ext.__class__.__name__))
|
raise ValueError('Unknown extension type: {}'.format(ext.__class__.__name__))
|
||||||
|
|
||||||
|
@@ -241,7 +241,7 @@ class ManagedCallback(object):
|
|||||||
except (KeyboardInterrupt, DeviceNotRespondingError, TimeoutError): # pylint: disable=W0703
|
except (KeyboardInterrupt, DeviceNotRespondingError, TimeoutError): # pylint: disable=W0703
|
||||||
raise
|
raise
|
||||||
except Exception as e: # pylint: disable=W0703
|
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
|
global failures_detected # pylint: disable=W0603
|
||||||
failures_detected = True
|
failures_detected = True
|
||||||
if isinstance(e, WAError):
|
if isinstance(e, WAError):
|
||||||
@@ -396,4 +396,3 @@ class Instrument(Extension):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Instrument({})'.format(self.name)
|
return 'Instrument({})'.format(self.name)
|
||||||
|
|
||||||
|
@@ -38,8 +38,8 @@ class GetterPriority(object):
|
|||||||
"""
|
"""
|
||||||
cached = 20
|
cached = 20
|
||||||
preferred = 10
|
preferred = 10
|
||||||
remote = 5
|
|
||||||
environment = 0
|
environment = 0
|
||||||
|
remote = -4
|
||||||
external_package = -5
|
external_package = -5
|
||||||
package = -10
|
package = -10
|
||||||
|
|
||||||
|
@@ -327,4 +327,3 @@ class Metric(object):
|
|||||||
return '<{}>'.format(result)
|
return '<{}>'.format(result)
|
||||||
|
|
||||||
__repr__ = __str__
|
__repr__ = __str__
|
||||||
|
|
||||||
|
@@ -186,4 +186,3 @@ def send(signal, sender, *args, **kwargs):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
dispatcher.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'])
|
VersionTuple = namedtuple('Version', ['major', 'minor', 'revision'])
|
||||||
|
|
||||||
version = VersionTuple(2, 5, 0)
|
version = VersionTuple(2, 7, 0)
|
||||||
|
|
||||||
|
|
||||||
def get_wa_version():
|
def get_wa_version():
|
||||||
|
@@ -37,6 +37,7 @@ class Workload(Extension):
|
|||||||
supported_devices = []
|
supported_devices = []
|
||||||
supported_platforms = []
|
supported_platforms = []
|
||||||
summary_metrics = []
|
summary_metrics = []
|
||||||
|
requires_network = False
|
||||||
|
|
||||||
def __init__(self, device, **kwargs):
|
def __init__(self, device, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -69,7 +70,7 @@ class Workload(Extension):
|
|||||||
"""
|
"""
|
||||||
pass
|
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
|
Perform the setup necessary to run the workload, such as copying the necessary files
|
||||||
to the device, configuring the environments, etc.
|
to the device, configuring the environments, etc.
|
||||||
@@ -78,7 +79,8 @@ class Workload(Extension):
|
|||||||
the workload.
|
the workload.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
if self.requires_network:
|
||||||
|
self.check_network_connected()
|
||||||
|
|
||||||
def run(self, context):
|
def run(self, context):
|
||||||
"""Execute the workload. This is the method that performs the actual "work" of the"""
|
"""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):
|
def finalize(self, context):
|
||||||
pass
|
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):
|
def __str__(self):
|
||||||
return '<Workload {}>'.format(self.name)
|
return '<Workload {}>'.format(self.name)
|
||||||
|
|
||||||
|
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -218,4 +218,3 @@ class Juno(BigLittleDevice):
|
|||||||
def get_android_id(self):
|
def get_android_id(self):
|
||||||
# Android ID currenlty not set properly in Juno Android builds.
|
# Android ID currenlty not set properly in Juno Android builds.
|
||||||
return 'abad1deadeadbeef'
|
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'),
|
description='Serial port on which the device is connected'),
|
||||||
Parameter('baudrate', default=115200, kind=int, description='Serial connection baud rate'),
|
Parameter('baudrate', default=115200, kind=int, description='Serial connection baud rate'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -847,4 +847,3 @@ def _slow_sendline(target, line):
|
|||||||
target.send(c)
|
target.send(c)
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
target.sendline('')
|
target.sendline('')
|
||||||
|
|
||||||
|
@@ -33,4 +33,3 @@ class Xe503c12Chormebook(LinuxDevice):
|
|||||||
]
|
]
|
||||||
|
|
||||||
abi = 'armeabi'
|
abi = 'armeabi'
|
||||||
|
|
||||||
|
@@ -12,5 +12,3 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -97,4 +97,3 @@ class ChromeOsDevice(LinuxDevice):
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
self.ui_status = None
|
self.ui_status = None
|
||||||
|
|
||||||
|
@@ -32,4 +32,3 @@ class OdroidXU3LinuxDevice(LinuxDevice):
|
|||||||
]
|
]
|
||||||
|
|
||||||
abi = 'armeabi'
|
abi = 'armeabi'
|
||||||
|
|
||||||
|
@@ -55,4 +55,3 @@ logObserver.start()
|
|||||||
|
|
||||||
def start_logging(level, fmt='%(asctime)s %(levelname)-8s: %(message)s'):
|
def start_logging(level, fmt='%(asctime)s %(levelname)-8s: %(message)s'):
|
||||||
logging.basicConfig(level=getattr(logging, level), format=fmt)
|
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
|
# CROSS_COMPILE=aarch64-linux-gnu- make
|
||||||
#
|
#
|
||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-static -lc
|
|
||||||
|
ifdef DEBUG
|
||||||
|
CFLAGS=-static -lc -g
|
||||||
|
else
|
||||||
|
CFLAGS=-static -lc -O2
|
||||||
|
endif
|
||||||
|
|
||||||
revent: revent.c
|
revent: revent.c
|
||||||
$(CROSS_COMPILE)$(CC) $(CFLAGS) revent.c -o revent
|
$(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
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.
|
# 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 app/build/outputs/aar/app-debug.aar ../../common/android/uiauto.aar
|
||||||
|
|
||||||
cp bin/classes/com/arm/wlauto/uiauto/BaseUiAutomation.class ../../common/android
|
|
||||||
|
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>
|
|
BIN
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
wlauto/external/uiauto/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
wlauto/external/uiauto/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/external/uiauto/gradlew
vendored
Executable file
160
wlauto/external/uiauto/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/external/uiauto/gradlew.bat
vendored
Normal file
90
wlauto/external/uiauto/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
|
14
wlauto/external/uiauto/project.properties
vendored
14
wlauto/external/uiauto/project.properties
vendored
@@ -1,14 +0,0 @@
|
|||||||
# This file is automatically generated by Android Tools.
|
|
||||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
|
||||||
#
|
|
||||||
# This file must be checked in Version Control Systems.
|
|
||||||
#
|
|
||||||
# To customize properties used by the Ant build system edit
|
|
||||||
# "ant.properties", and override values to adapt the script to your
|
|
||||||
# project structure.
|
|
||||||
#
|
|
||||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
|
||||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
|
||||||
|
|
||||||
# Project target.
|
|
||||||
target=android-17
|
|
1
wlauto/external/uiauto/settings.gradle
vendored
Normal file
1
wlauto/external/uiauto/settings.gradle
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
include ':app'
|
@@ -1,152 +0,0 @@
|
|||||||
/* Copyright 2013-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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package com.arm.wlauto.uiauto;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
public class BaseUiAutomation extends UiAutomatorTestCase {
|
|
||||||
|
|
||||||
|
|
||||||
public void sleep(int second) {
|
|
||||||
super.sleep(second * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean takeScreenshot(String name) {
|
|
||||||
Bundle params = getParams();
|
|
||||||
String png_dir = params.getString("workdir");
|
|
||||||
|
|
||||||
try {
|
|
||||||
return getUiDevice().takeScreenshot(new File(png_dir, 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 text_obj = new UiObject(selector.text(text)
|
|
||||||
.className("android.widget.TextView"));
|
|
||||||
waitObject(text_obj, 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("Timed out waiting for Logcat text \"%s\"".format(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -18,14 +18,14 @@ from wlauto.core import instrumentation
|
|||||||
|
|
||||||
def instrument_is_installed(instrument):
|
def instrument_is_installed(instrument):
|
||||||
"""Returns ``True`` if the specified instrument is installed, and ``False``
|
"""Returns ``True`` if the specified instrument is installed, and ``False``
|
||||||
other wise. The insturment maybe specified either as a name or a subclass (or
|
other wise. The instrument maybe specified either as a name or a subclass (or
|
||||||
instance of subclass) of :class:`wlauto.core.Instrument`."""
|
instance of subclass) of :class:`wlauto.core.Instrument`."""
|
||||||
return instrumentation.is_installed(instrument)
|
return instrumentation.is_installed(instrument)
|
||||||
|
|
||||||
|
|
||||||
def instrument_is_enabled(instrument):
|
def instrument_is_enabled(instrument):
|
||||||
"""Returns ``True`` if the specified instrument is installed and is currently
|
"""Returns ``True`` if the specified instrument is installed and is currently
|
||||||
enabled, and ``False`` other wise. The insturment maybe specified either
|
enabled, and ``False`` other wise. The instrument maybe specified either
|
||||||
as a name or a subclass (or instance of subclass) of
|
as a name or a subclass (or instance of subclass) of
|
||||||
:class:`wlauto.core.Instrument`."""
|
:class:`wlauto.core.Instrument`."""
|
||||||
return instrumentation.is_enabled(instrument)
|
return instrumentation.is_enabled(instrument)
|
||||||
|
131
wlauto/instrumentation/acmecape/__init__.py
Normal file
131
wlauto/instrumentation/acmecape/__init__.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#pylint: disable=attribute-defined-outside-init
|
||||||
|
from __future__ import division
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
from fcntl import fcntl, F_GETFL, F_SETFL
|
||||||
|
from string import Template
|
||||||
|
from subprocess import Popen, PIPE, STDOUT
|
||||||
|
|
||||||
|
from wlauto import Instrument, Parameter
|
||||||
|
from wlauto.exceptions import HostError
|
||||||
|
from wlauto.utils.misc import which
|
||||||
|
|
||||||
|
|
||||||
|
IIOCAP_CMD_TEMPLATE = Template("""
|
||||||
|
${iio_capture} -n ${host} -b ${buffer_size} -c -f ${outfile} ${iio_device}
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_nonblock(pipe, size=1024):
|
||||||
|
fd = pipe.fileno()
|
||||||
|
flags = fcntl(fd, F_GETFL)
|
||||||
|
flags |= os.O_NONBLOCK
|
||||||
|
fcntl(fd, F_SETFL, flags)
|
||||||
|
|
||||||
|
output = ''
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
output += pipe.read(size)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
class AcmeCapeInstrument(Instrument):
|
||||||
|
|
||||||
|
name = 'acmecape'
|
||||||
|
description = """
|
||||||
|
Instrumetnation for the BayLibre ACME cape for power/energy measurment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parameters = [
|
||||||
|
Parameter('iio-capture', default=which('iio-capture'),
|
||||||
|
description="""
|
||||||
|
Path to the iio-capture binary will be taken from the
|
||||||
|
environment, if not specfied.
|
||||||
|
"""),
|
||||||
|
Parameter('host', default='baylibre-acme.local',
|
||||||
|
description="""
|
||||||
|
Host name (or IP address) of the ACME cape board.
|
||||||
|
"""),
|
||||||
|
Parameter('iio-device', default='iio:device0',
|
||||||
|
description="""
|
||||||
|
"""),
|
||||||
|
Parameter('buffer-size', kind=int, default=256,
|
||||||
|
description="""
|
||||||
|
Size of the capture buffer (in KB).
|
||||||
|
"""),
|
||||||
|
]
|
||||||
|
|
||||||
|
def initialize(self, context):
|
||||||
|
if self.iio_capture is None:
|
||||||
|
raise HostError('Missing iio-capture binary')
|
||||||
|
self.command = None
|
||||||
|
self.subprocess = None
|
||||||
|
|
||||||
|
def setup(self, context):
|
||||||
|
self.outfile = os.path.join(context.output_directory, 'acme-capture.csv')
|
||||||
|
params = dict(
|
||||||
|
iio_capture=self.iio_capture,
|
||||||
|
host=self.host,
|
||||||
|
buffer_size=self.buffer_size,
|
||||||
|
iio_device=self.iio_device,
|
||||||
|
outfile=self.outfile,
|
||||||
|
)
|
||||||
|
self.command = IIOCAP_CMD_TEMPLATE.substitute(**params)
|
||||||
|
self.logger.debug('ACME cape command: {}'.format(self.command))
|
||||||
|
|
||||||
|
def very_fast_start(self, context): # pylint: disable=unused-argument
|
||||||
|
self.subprocess = Popen(self.command.split(), stdout=PIPE, stderr=STDOUT)
|
||||||
|
|
||||||
|
def very_fast_stop(self, context): # pylint: disable=unused-argument
|
||||||
|
self.subprocess.terminate()
|
||||||
|
|
||||||
|
def update_result(self, context):
|
||||||
|
timeout_secs = 10
|
||||||
|
for _ in xrange(timeout_secs):
|
||||||
|
if self.subprocess.poll() is not None:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
output = _read_nonblock(self.subprocess.stdout)
|
||||||
|
self.subprocess.kill()
|
||||||
|
self.logger.error('iio-capture did not terminate gracefully')
|
||||||
|
if self.subprocess.poll() is None:
|
||||||
|
msg = 'Could not terminate iio-capture:\n{}'
|
||||||
|
raise HostError(msg.format(output))
|
||||||
|
if not os.path.isfile(self.outfile):
|
||||||
|
raise HostError('Output CSV not generated.')
|
||||||
|
|
||||||
|
context.add_iteration_artifact('iio-capture', self.outfile, 'data')
|
||||||
|
if os.stat(self.outfile).st_size == 0:
|
||||||
|
self.logger.warning('"{}" appears to be empty'.format(self.outfile))
|
||||||
|
return
|
||||||
|
self._compute_stats(context)
|
||||||
|
|
||||||
|
def _compute_stats(self, context):
|
||||||
|
with open(self.outfile, 'rb') as fh:
|
||||||
|
reader = csv.reader(fh, skipinitialspace=True)
|
||||||
|
header = reader.next()
|
||||||
|
power_index = header.index('power mW')
|
||||||
|
ts_index = header.index('timestamp ms')
|
||||||
|
|
||||||
|
last_ts = 0.0
|
||||||
|
energy_uj = 0
|
||||||
|
ave_power_mw = 0.0
|
||||||
|
|
||||||
|
for i, row in enumerate(reader):
|
||||||
|
row_power_mw = float(row[power_index])
|
||||||
|
row_ts = float(row[ts_index])
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
ave_power_mw = row_power_mw
|
||||||
|
else:
|
||||||
|
ave_power_mw = ave_power_mw + (row_power_mw - ave_power_mw) / i
|
||||||
|
energy_uj += row_power_mw * (row_ts - last_ts)
|
||||||
|
last_ts = row_ts
|
||||||
|
|
||||||
|
context.add_metric('power', ave_power_mw, 'milliwatts')
|
||||||
|
context.add_metric('energy', energy_uj / 1000000, 'joules')
|
@@ -112,10 +112,10 @@ class Daq(Instrument):
|
|||||||
Parameter('server_host', kind=str, default='localhost',
|
Parameter('server_host', kind=str, default='localhost',
|
||||||
global_alias='daq_server_host',
|
global_alias='daq_server_host',
|
||||||
description='The host address of the machine that runs the daq Server which the '
|
description='The host address of the machine that runs the daq Server which the '
|
||||||
'insturment communicates with.'),
|
'instrument communicates with.'),
|
||||||
Parameter('server_port', kind=int, default=45677,
|
Parameter('server_port', kind=int, default=45677,
|
||||||
global_alias='daq_server_port',
|
global_alias='daq_server_port',
|
||||||
description='The port number for daq Server in which daq insturment communicates '
|
description='The port number for daq Server in which daq instrument communicates '
|
||||||
'with.'),
|
'with.'),
|
||||||
Parameter('device_id', kind=str, default='Dev1',
|
Parameter('device_id', kind=str, default='Dev1',
|
||||||
global_alias='daq_device_id',
|
global_alias='daq_device_id',
|
||||||
@@ -266,7 +266,7 @@ class Daq(Instrument):
|
|||||||
metric_name = '{}_{}'.format(port, metric)
|
metric_name = '{}_{}'.format(port, metric)
|
||||||
context.result.add_metric(metric_name, round(value, 3), UNITS[metric])
|
context.result.add_metric(metric_name, round(value, 3), UNITS[metric])
|
||||||
self._results[key][metric_name] = round(value, 3)
|
self._results[key][metric_name] = round(value, 3)
|
||||||
energy = sum(data[metrics.index('power')]) * (self.sampling_rate / 1000000)
|
energy = sum(data[metrics.index('power')]) / self.sampling_rate
|
||||||
context.result.add_metric('{}_energy'.format(port), round(energy, 3), UNITS['energy'])
|
context.result.add_metric('{}_energy'.format(port), round(energy, 3), UNITS['energy'])
|
||||||
|
|
||||||
def teardown(self, context):
|
def teardown(self, context):
|
||||||
|
@@ -196,4 +196,3 @@ class DelayInstrument(Instrument):
|
|||||||
if self.active_cooling and not self.device.has('active_cooling'):
|
if self.active_cooling and not self.device.has('active_cooling'):
|
||||||
message = 'Your device does not support active cooling. Did you configure it with an approprite module?'
|
message = 'Your device does not support active cooling. Did you configure it with an approprite module?'
|
||||||
raise InstrumentError(message)
|
raise InstrumentError(message)
|
||||||
|
|
||||||
|
@@ -58,5 +58,3 @@ class DmesgInstrument(Instrument):
|
|||||||
def teardown(self, context): # pylint: disable=unused-argument
|
def teardown(self, context): # pylint: disable=unused-argument
|
||||||
if self.loglevel:
|
if self.loglevel:
|
||||||
self.device.set_sysfile_value(self.loglevel_file, self.old_loglevel, verify=False)
|
self.device.set_sysfile_value(self.loglevel_file, self.old_loglevel, verify=False)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -348,7 +348,7 @@ class EnergyModelInstrument(Instrument):
|
|||||||
desicription = """
|
desicription = """
|
||||||
Generates a power mode for the device based on specified workload.
|
Generates a power mode for the device based on specified workload.
|
||||||
|
|
||||||
This insturment will execute the workload specified by the agenda (currently, only ``sysbench`` is
|
This instrument will execute the workload specified by the agenda (currently, only ``sysbench`` is
|
||||||
supported) and will use the resulting performance and power measurments to generate a power mode for
|
supported) and will use the resulting performance and power measurments to generate a power mode for
|
||||||
the device.
|
the device.
|
||||||
|
|
||||||
@@ -650,7 +650,7 @@ class EnergyModelInstrument(Instrument):
|
|||||||
def enable_all_cores(self):
|
def enable_all_cores(self):
|
||||||
counter = Counter(self.device.core_names)
|
counter = Counter(self.device.core_names)
|
||||||
for core, number in counter.iteritems():
|
for core, number in counter.iteritems():
|
||||||
self.device.set_number_of_online_cpus(core, number)
|
self.device.set_number_of_online_cores(core, number)
|
||||||
self.big_cpus = self.device.get_online_cpus(self.big_core)
|
self.big_cpus = self.device.get_online_cpus(self.big_core)
|
||||||
self.little_cpus = self.device.get_online_cpus(self.little_core)
|
self.little_cpus = self.device.get_online_cpus(self.little_core)
|
||||||
|
|
||||||
@@ -725,7 +725,8 @@ class EnergyModelInstrument(Instrument):
|
|||||||
if not self.no_hotplug:
|
if not self.no_hotplug:
|
||||||
spec.runtime_parameters['{}_cores'.format(core)] = num_cpus
|
spec.runtime_parameters['{}_cores'.format(core)] = num_cpus
|
||||||
spec.runtime_parameters['{}_frequency'.format(core)] = min_frequency
|
spec.runtime_parameters['{}_frequency'.format(core)] = min_frequency
|
||||||
spec.runtime_parameters['ui'] = 'off'
|
if self.device.platform == 'chromeos':
|
||||||
|
spec.runtime_parameters['ui'] = 'off'
|
||||||
spec.cluster = cluster
|
spec.cluster = cluster
|
||||||
spec.num_cpus = num_cpus
|
spec.num_cpus = num_cpus
|
||||||
spec.id = '{}_idle_{}_{}'.format(cluster, state.id, num_cpus)
|
spec.id = '{}_idle_{}_{}'.format(cluster, state.id, num_cpus)
|
||||||
@@ -744,7 +745,8 @@ class EnergyModelInstrument(Instrument):
|
|||||||
spec.runtime_parameters['{}_frequency'.format(core)] = freq
|
spec.runtime_parameters['{}_frequency'.format(core)] = freq
|
||||||
if not self.no_hotplug:
|
if not self.no_hotplug:
|
||||||
spec.runtime_parameters['{}_cores'.format(core)] = num_cpus
|
spec.runtime_parameters['{}_cores'.format(core)] = num_cpus
|
||||||
spec.runtime_parameters['ui'] = 'off'
|
if self.device.platform == 'chromeos':
|
||||||
|
spec.runtime_parameters['ui'] = 'off'
|
||||||
spec.id = '{}_{}_{}'.format(cluster, num_cpus, freq)
|
spec.id = '{}_{}_{}'.format(cluster, num_cpus, freq)
|
||||||
spec.label = 'freq_{}_{}'.format(cluster, spec.label)
|
spec.label = 'freq_{}_{}'.format(cluster, spec.label)
|
||||||
spec.workload_parameters['taskset_mask'] = list_to_mask(self.get_cpus(cluster))
|
spec.workload_parameters['taskset_mask'] = list_to_mask(self.get_cpus(cluster))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user