mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-09-04 04:12:42 +01:00
Compare commits
537 Commits
v2.4.0
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
|
601202c2ec | ||
|
a81fe5555a | ||
|
d6eb8b6faf | ||
|
490dd404ae | ||
|
60f52c2187 | ||
|
fbb9908298 | ||
|
01fa0a3571 | ||
|
be2b14a575 | ||
|
dab2bbb1c7 | ||
|
340ae72ca2 | ||
|
e64dc8bc26 | ||
|
cb48ece77f | ||
|
8f67b7f94b | ||
|
fa553ee430 | ||
|
01c9c88e79 | ||
|
0c32e39ce0 | ||
|
1364ec05e8 | ||
|
d5c888cc90 | ||
|
d6ab68bffc | ||
|
30e9b553ff | ||
|
6a3f441064 | ||
|
13cbe2f059 | ||
|
53b173c55f | ||
|
f598c60514 | ||
|
ceda8e74bf | ||
|
173c71b867 | ||
|
d88d35be26 | ||
|
599452d41f | ||
|
33dae51536 | ||
|
f8950dea33 | ||
|
136d1fef0f | ||
|
5204383582 | ||
|
bfa1d8dd62 | ||
|
b75fdf85d0 | ||
|
fcbb83f5ac | ||
|
807003128e | ||
|
3e4d068eff | ||
|
a3936afb4c | ||
|
24000a21df | ||
|
e5c0ca85f0 | ||
|
51f07c4473 | ||
|
ffde7401ef | ||
|
b4026ae390 | ||
|
f76c00dd99 | ||
|
b1e375a676 | ||
|
1102ba1679 | ||
|
6d999301f3 | ||
|
e4fdf0bdb9 | ||
|
d493b1e790 | ||
|
47e31765b4 | ||
|
79faec120e | ||
|
66dbe7a508 | ||
|
80a780dcfe | ||
|
694d51ffb6 | ||
|
1befe63e45 | ||
|
27b08bade0 | ||
|
1477a89ee4 | ||
|
0dfbbae7b6 | ||
|
a8a8d21de6 | ||
|
f467f6f991 | ||
|
4352e02806 | ||
|
693d0544a3 | ||
|
eb239c65d0 | ||
|
f7e4232eaa | ||
|
8cbf189029 | ||
|
6e45e1a039 | ||
|
b6f770cfc5 | ||
|
c7de8cabd6 | ||
|
fd7df36a5a | ||
|
607187ad5b | ||
|
b4036c5f15 | ||
|
b6e077c06b | ||
|
8e0b793f89 | ||
|
8b82451230 | ||
|
64c352fab6 | ||
|
68697a42a7 | ||
|
ae4ae3da5e | ||
|
393abc267f | ||
|
751970f991 | ||
|
254e9fff38 | ||
|
088709f290 | ||
|
850fcb24ab | ||
|
ace41d10a5 | ||
|
cb53fe9ec8 | ||
|
4e161127e1 | ||
|
bf43bf93bc | ||
|
7a19046645 | ||
|
5db11462be | ||
|
7bf0e3c344 | ||
|
d16d8bf62d | ||
|
c93cc81aac | ||
|
9491763aa7 | ||
|
a172c8f624 | ||
|
33286ba982 | ||
|
f69e4c5b18 | ||
|
5b543d2edf | ||
|
17edb13eb9 | ||
|
40d281b336 | ||
|
74ea78aa42 | ||
|
8edce40301 | ||
|
642d757066 | ||
|
969201968e | ||
|
3f76920fa9 | ||
|
46b78d35be | ||
|
ba34b973ac | ||
|
6d173f2f3f | ||
|
8b1d2c9fe9 | ||
|
d3c59e2f74 | ||
|
fce04d2938 | ||
|
27df426c0d | ||
|
f8966bf324 | ||
|
e076d47a7b | ||
|
ea798aefb3 | ||
|
dcf13f8c2c | ||
|
f99c6f5656 | ||
|
359d9d3e5f | ||
|
d56f581a0a | ||
|
f12cf6d557 | ||
|
311c4e419f | ||
|
2becd94381 | ||
|
1daa7f97c0 | ||
|
7af5868c22 | ||
|
2e1ce49170 | ||
|
23eb357e9e | ||
|
71c5d23d97 | ||
|
edfef444fb | ||
|
3a7a5276e4 | ||
|
f179b09978 | ||
|
620fbfdd2a | ||
|
4213e8e7d1 | ||
|
9fffa7958a | ||
|
0f2bc17eca | ||
|
558e40698b | ||
|
41b52178bb | ||
|
8aa1bdc63d | ||
|
8355fcf886 | ||
|
fa7d89d734 | ||
|
4649fa13db | ||
|
10dd2b304e | ||
|
93fbb7282a | ||
|
edc26fe75c | ||
|
6aecf1b35e | ||
|
4c5e008609 | ||
|
5bed658045 | ||
|
8ac5657993 | ||
|
fc26daecfc | ||
|
0232341445 | ||
|
3b052cc619 | ||
|
3a9505d54e | ||
|
4e94ff9ed7 | ||
|
c47ae5cfcf | ||
|
ce11b94f28 | ||
|
765fdd7cbb | ||
|
77d724efa3 | ||
|
acb9dd61e7 | ||
|
cc7684986a | ||
|
e69aea4e69 | ||
|
2b6f036d9a | ||
|
5738d19114 | ||
|
53ae47bff3 | ||
|
1c8e18bf36 | ||
|
5dbf7e7d38 | ||
|
a2945d58cb | ||
|
8727fe514a | ||
|
6465e732fd | ||
|
f9ec869c7b | ||
|
1bbd3ef87a | ||
|
486ade6499 | ||
|
1a23bd03a2 | ||
|
5018a1ec94 | ||
|
fe58245843 | ||
|
006bf6387f | ||
|
19569816d3 | ||
|
1dfbaf4ebe | ||
|
5b7d61b4b9 | ||
|
b5dc5b8648 | ||
|
2202326c02 | ||
|
8608c3b747 | ||
|
9afe084f2c | ||
|
83ab1ac441 | ||
|
ea1d13c37f | ||
|
20996e9a58 | ||
|
3711f7316d | ||
|
d279cc7453 | ||
|
f6b8fd3f4b | ||
|
ff2f88fbd7 | ||
|
96f4ade874 | ||
|
ac0256e377 | ||
|
793af6253f | ||
|
5ef7d2dd44 | ||
|
cf8cb5bfab | ||
|
9376c6875b | ||
|
a6347f5833 | ||
|
38a7e01e83 | ||
|
e18366b3f8 | ||
|
b9701201a3 | ||
|
441ba974b7 | ||
|
a33df50ce8 | ||
|
52d4635fe8 | ||
|
14924ec6f4 | ||
|
3d610788a3 | ||
|
1986511ae8 | ||
|
175e7f3cc0 | ||
|
392a3f1600 | ||
|
b5c0bdb0eb | ||
|
502b0ed4b3 | ||
|
cab9d918ab | ||
|
e686e89b39 | ||
|
b510b31052 | ||
|
5b59d101ef | ||
|
7713f02252 | ||
|
0a2afdfd84 | ||
|
530714c61c | ||
|
67f418f79f | ||
|
64860a2d1a | ||
|
f57dd83d1a | ||
|
3782a33060 | ||
|
8e27794124 | ||
|
9d4aa4983a | ||
|
b426e00f2f | ||
|
07d34e5615 | ||
|
b1ae5a5465 | ||
|
4ea4bc8631 | ||
|
fe259dca05 | ||
|
86f3066f56 | ||
|
0f579e18b3 | ||
|
25172fb027 | ||
|
550a0db61a | ||
|
73c2609a72 | ||
|
1ec7961b0e | ||
|
01f2a5f412 | ||
|
480a054860 | ||
|
e9ba9352a6 | ||
|
0a3ff099c0 | ||
|
75cc5854bf | ||
|
77aaa0b849 | ||
|
0945dd6ba4 | ||
|
4c94ba43ac | ||
|
efae2e8c32 | ||
|
59874b862d | ||
|
da19859c25 | ||
|
d87e425c24 | ||
|
2872080d1a | ||
|
625a3a39a5 | ||
|
aa2d187c4d | ||
|
b80e5dc52e | ||
|
2208d45bfb | ||
|
51e4e71931 | ||
|
0388fa6f36 | ||
|
ee7c04a568 | ||
|
9707aa6237 | ||
|
019ee34c0d | ||
|
873bdf0bc7 | ||
|
54c409ce6f | ||
|
a2d0747b4c | ||
|
25eac432c9 | ||
|
dd61f99785 | ||
|
164f207084 | ||
|
cb01b0c9a9 | ||
|
139a0698c9 | ||
|
259b813a96 | ||
|
299b28b3c1 | ||
|
ece33c1d68 | ||
|
f68cf4e317 | ||
|
c49c5c4121 | ||
|
b8d7956d4c | ||
|
fee872585f | ||
|
2dd3a2ba4d | ||
|
662033399f | ||
|
bb33123b17 | ||
|
fab6a977aa | ||
|
25dd6b71f3 | ||
|
246416d4d2 | ||
|
1fe037486f | ||
|
f27b500028 | ||
|
5a780e8211 | ||
|
60ca0649ab | ||
|
dbda128813 | ||
|
ff7a0626ce | ||
|
9a94c59605 | ||
|
d3dd9c849a | ||
|
12a78ce291 | ||
|
c8a735e298 | ||
|
071bf9fba7 | ||
|
ef919a0fa9 | ||
|
88b18dda07 | ||
|
242df842bc | ||
|
a6355885fc | ||
|
77a44f11c6 | ||
|
afeb726d53 | ||
|
7904e6b562 | ||
|
224b973ace | ||
|
8660d0f488 | ||
|
be7aa3d379 | ||
|
8503fea0ee | ||
|
4a15a41cf8 | ||
|
3a90309383 | ||
|
b48e5ce58a | ||
|
f33d6f4729 | ||
|
6f8989a8ba | ||
|
a826b661f4 | ||
|
43f4e52995 | ||
|
23b3b165d5 | ||
|
2f87e126f0 | ||
|
59d74b6273 | ||
|
7b92f355c8 | ||
|
982069be32 | ||
|
63ff8987ea | ||
|
f276d4e39f | ||
|
1811a8b733 | ||
|
0ae03e2c54 | ||
|
c423a8b4bc | ||
|
c207a34872 | ||
|
2cb40d3da6 | ||
|
18d1f9f649 | ||
|
17ce8d0fe9 | ||
|
ac03c9bab4 | ||
|
8bdffe6f9c | ||
|
2ff13089fd | ||
|
772346507c | ||
|
0fc88a84be | ||
|
6e4f6af942 | ||
|
c87daa510e | ||
|
5e1c9694e7 | ||
|
a9a42164a3 | ||
|
0d50fe9b77 | ||
|
e5c228bab2 | ||
|
7ccac87b93 | ||
|
24a2afb5b9 | ||
|
9652801cce | ||
|
881b7514e2 | ||
|
17fe6c9a5b | ||
|
f02b6d5fd9 | ||
|
eaf4d02aea | ||
|
56a4d52995 | ||
|
ec5c149df5 | ||
|
c0f32237e3 | ||
|
5a1c8c7a7e | ||
|
46cd26e774 | ||
|
544c498eb6 | ||
|
5ad75dd0b8 | ||
|
b2248413b7 | ||
|
9296bafbd9 | ||
|
8abf39762d | ||
|
87cbce4244 | ||
|
ef61f16896 | ||
|
e96450d226 | ||
|
2cf08cf448 | ||
|
59cfd7c757 | ||
|
d3c7f11f2d | ||
|
187fd70077 | ||
|
fe7f98a98b | ||
|
66c18fcd31 | ||
|
5773da0d08 | ||
|
d581f1f329 | ||
|
f165969d61 | ||
|
8dc24bd327 | ||
|
59066cb46d | ||
|
6c4d88ff57 | ||
|
a40542d57b | ||
|
697aefc7bb | ||
|
8bc71bb810 | ||
|
91210f26e9 | ||
|
44a49db04d | ||
|
0bfa4bff3c | ||
|
73aa590056 | ||
|
985b249a24 | ||
|
f5e138bed0 | ||
|
b6c0e2e4fd | ||
|
df8ef6be6b | ||
|
8a3186e1c8 | ||
|
68043f2a52 | ||
|
95bbce77a2 | ||
|
ec85f9f8a0 | ||
|
82e4998092 | ||
|
48259d872b | ||
|
8d13e1f341 | ||
|
33ef949507 | ||
|
68714e0e55 | ||
|
9ee1666a76 | ||
|
8dcdc9afe1 | ||
|
724f6e590e | ||
|
507090515b | ||
|
1dfbe9e44c | ||
|
d303ab2b50 | ||
|
b17ae78d6b | ||
|
391b0b01fc | ||
|
20861f0ee4 | ||
|
ff5f48b7e7 | ||
|
9a301175b0 | ||
|
712c79020d | ||
|
12dfbef76b | ||
|
b1f607ef70 | ||
|
107e8414bb | ||
|
4f8b7e9f59 | ||
|
a077e7df3c | ||
|
a2257fe1e2 | ||
|
50353d0b8f | ||
|
0f5621ff66 | ||
|
2eca77fb02 | ||
|
3de5b5fe0b | ||
|
499a9f4082 | ||
|
3043506d86 | ||
|
7db904b359 | ||
|
5abeb7aac2 | ||
|
e04691afb9 | ||
|
15ced50640 | ||
|
1a2e1fdf75 | ||
|
3531dd6d07 | ||
|
cf55f317f8 | ||
|
79554a2dbc | ||
|
06c232545a | ||
|
11184750ec | ||
|
77b221fc5a | ||
|
20cd6a9c18 | ||
|
34d7e7055a | ||
|
0c1e01cad4 | ||
|
a68e46eb0a | ||
|
203a3f7d07 | ||
|
de133cddb4 | ||
|
a5c9b94257 | ||
|
c203ec8921 | ||
|
de021da300 | ||
|
693afa3528 | ||
|
5203188d9e | ||
|
08663209d6 | ||
|
232e4b3e65 | ||
|
13ebc8ad55 | ||
|
759f8db1bc | ||
|
9a7cccacab | ||
|
288aa764b3 | ||
|
a32cc0f213 | ||
|
fdbc2ae372 | ||
|
9129a9d2d8 | ||
|
cb46c57754 | ||
|
536c0ffe4e | ||
|
4f30e37f22 | ||
|
0deb8fd7c6 | ||
|
85edc3084b | ||
|
3a99a284c4 | ||
|
5e3cc8fcb5 | ||
|
f92bd1bcdd | ||
|
519efaf22c | ||
|
28ef01505d | ||
|
dec574e59e | ||
|
7ad8b8522b | ||
|
14a1bc8a5d | ||
|
45a9c0a86d | ||
|
7edb2c8919 | ||
|
5fad83a50d | ||
|
68fefe8532 | ||
|
c96590b713 | ||
|
dc22856431 | ||
|
2d1f0e99b9 | ||
|
da720c8613 | ||
|
eaabe01fa5 | ||
|
dc07c8d87e | ||
|
a402bfd7f9 | ||
|
fe2d279eac | ||
|
0ffbac1629 | ||
|
65cc22a305 | ||
|
2ae8c6073f | ||
|
dc5cf6d7b8 | ||
|
e6ae9ecc51 | ||
|
85fb5e3684 | ||
|
98b19328de | ||
|
73ddc205fc | ||
|
1e6eaff702 | ||
|
78d49ca8ae | ||
|
f4c89644ff | ||
|
798a7befb8 | ||
|
6a388ffc71 | ||
|
82df73278e | ||
|
68a39d7fa1 | ||
|
120f0ff94f | ||
|
f47ba6fea6 | ||
|
5f8da66322 | ||
|
67213d471b | ||
|
7c35c604f4 | ||
|
c11cc7d0d2 | ||
|
89f1e7b6e5 | ||
|
bd826783cc | ||
|
0fb867e7c6 | ||
|
6b3187c2c9 | ||
|
75ce620e6b | ||
|
d9c4063307 | ||
|
5f2b25532b | ||
|
0998c18efd | ||
|
9eeeaf02ad | ||
|
df937dc847 | ||
|
1ef7bb4e93 | ||
|
41890589e1 | ||
|
a0cd66ed45 | ||
|
b84f97a902 | ||
|
ffc3fcef67 | ||
|
09563bc01e | ||
|
f1bb44b3e7 | ||
|
1085c715c2 | ||
|
c105e8357c | ||
|
dc1b0e629e | ||
|
62a0fd70de | ||
|
438e18328d | ||
|
57b31149f1 | ||
|
09390e7ffb | ||
|
e83d021a5c | ||
|
bca012fccb | ||
|
bb37c31fed | ||
|
0005f927e8 | ||
|
9222257d79 | ||
|
585d8b2d7d | ||
|
d3470dca73 | ||
|
0f60e9600f | ||
|
6a85dff94f | ||
|
aae88b8be4 | ||
|
72a617c16d | ||
|
d6355966bf | ||
|
845d577482 | ||
|
9ccf256ee8 | ||
|
cc9b00673e | ||
|
e7c75b2d3b | ||
|
480155fd8c | ||
|
d98bdac0be | ||
|
32cf5c0939 | ||
|
a330a64340 | ||
|
bef8fb40ef | ||
|
344bc519c4 | ||
|
3da58d9541 | ||
|
065ebaac61 | ||
|
f85ef61ce9 | ||
|
e5c6ef5368 | ||
|
a697c47c49 | ||
|
c6e712d44c | ||
|
00c9bdc2a6 | ||
|
1b31d8ef6f | ||
|
b3a9512f44 | ||
|
a06016a442 | ||
|
c02a1118d7 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -14,9 +14,9 @@ wa_output/
|
|||||||
doc/source/api/
|
doc/source/api/
|
||||||
doc/source/extensions/
|
doc/source/extensions/
|
||||||
MANIFEST
|
MANIFEST
|
||||||
wlauto/external/uiautomator/bin/
|
wlauto/external/uiauto/bin/
|
||||||
wlauto/external/uiautomator/*.properties
|
wlauto/external/uiauto/*.properties
|
||||||
wlauto/external/uiautomator/build.xml
|
wlauto/external/uiauto/build.xml
|
||||||
*.orig
|
*.orig
|
||||||
local.properties
|
local.properties
|
||||||
wlauto/external/revent/libs/
|
wlauto/external/revent/libs/
|
||||||
|
@@ -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"
|
@@ -35,10 +35,10 @@ compare_versions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pylint_version=$(python -c 'from pylint.__pkginfo__ import version; print version')
|
pylint_version=$(python -c 'from pylint.__pkginfo__ import version; print version')
|
||||||
compare_versions $pylint_version "1.3.0"
|
compare_versions $pylint_version "1.5.1"
|
||||||
result=$?
|
result=$?
|
||||||
if [ "$result" == "2" ]; then
|
if [ "$result" == "2" ]; then
|
||||||
echo "ERROR: pylint version must be at least 1.3.0; found $pylint_version"
|
echo "ERROR: pylint version must be at least 1.5.1; found $pylint_version"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@@ -6,10 +6,10 @@ Modules
|
|||||||
|
|
||||||
Modules are essentially plug-ins for Extensions. They provide a way of defining
|
Modules are essentially plug-ins for Extensions. They provide a way of defining
|
||||||
common and reusable functionality. An Extension can load zero or more modules
|
common and reusable functionality. An Extension can load zero or more modules
|
||||||
during it's creation. Loaded modules will then add their capabilities (see
|
during its creation. Loaded modules will then add their capabilities (see
|
||||||
Capabilities_) to those of the Extension. When calling code tries to access an
|
Capabilities_) to those of the Extension. When calling code tries to access an
|
||||||
attribute of an Extension the Extension doesn't have, it will try to find the
|
attribute of an Extension the Extension doesn't have, it will try to find the
|
||||||
attribute among it's loaded modules and will return that instead.
|
attribute among its loaded modules and will return that instead.
|
||||||
|
|
||||||
.. note:: Modules are themselves extensions, and can therefore load their own
|
.. note:: Modules are themselves extensions, and can therefore load their own
|
||||||
modules. *Do not* abuse this.
|
modules. *Do not* abuse this.
|
||||||
|
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_HOME/dependencies/SOME_WORKLOAD/`` directory. (by default
|
||||||
|
``~/.workload_automation/dependencies/SOME_WORKLOAD/``). The ``WA_HOME`` enviroment variable can be used
|
||||||
|
to chnage the location of this folder. The APK files need to be put into the corresponding directories
|
||||||
|
for the workload they belong to. The name of the file can be anything but as explained below may need
|
||||||
|
to contain certain peices of information.
|
||||||
|
|
||||||
|
All ApkWorkloads have parameters that affect the way in which APK files are resolved, ``exact_abi``,
|
||||||
|
``force_install`` and ``check_apk``. Their exact behaviours are outlined below.
|
||||||
|
|
||||||
|
.. confval:: exact_abi
|
||||||
|
|
||||||
|
If this setting is enabled WA's resource resolvers will look for the devices ABI with any native
|
||||||
|
code present in the apk. By default this setting is disabled since most apks will work across all
|
||||||
|
devices. You may wish to enable this feature when working with devices that support multiple ABI's (like
|
||||||
|
64-bit devices that can run 32-bit APK files) and are specifically trying to test one or the other.
|
||||||
|
|
||||||
|
.. confval:: force_install
|
||||||
|
|
||||||
|
If this setting is enabled WA will *always* use the APK file on the host, and re-install it on every
|
||||||
|
iteration. If there is no APK on the host that is a suitable version and/or ABI for the workload WA
|
||||||
|
will error when ``force_install`` is enabled.
|
||||||
|
|
||||||
|
.. confval:: check_apk
|
||||||
|
|
||||||
|
This parameter is used to specify a preference over host or target versions of the app. When set to
|
||||||
|
``True`` WA will prefer the host side version of the APK. It will check if the host has the APK and
|
||||||
|
if the host APK meets the version requirements of the workload. If does and the target already has
|
||||||
|
same version nothing will be done, other wise it will overwrite the targets app with the host version.
|
||||||
|
If the hosts is missing the APK or it does not meet version requirements WA will fall back to the app
|
||||||
|
on the target if it has the app and it is of a suitable version. When this parameter is set to
|
||||||
|
``false`` WA will prefer to use the version already on the target if it meets the workloads version
|
||||||
|
requirements. If it does not it will fall back to search the host for the correct version. In both modes
|
||||||
|
if neither the host nor target have a suitable version, WA will error and not run the workload.
|
||||||
|
|
||||||
|
Some workloads will also feature the follow parameters which will alter the way their APK files are resolved.
|
||||||
|
|
||||||
|
.. confval:: version
|
||||||
|
|
||||||
|
This parameter is used to specify which version of uiautomation for the workload is used. In some workloads
|
||||||
|
e.g. ``geekbench`` multiple versions with drastically different UI's are supported. When a workload uses a
|
||||||
|
version it is required for the APK file to contain the uiautomation version in the file name. In the case
|
||||||
|
of antutu the file names could be: ``geekbench_2.apk`` or ``geekbench_3.apk``.
|
||||||
|
|
||||||
|
.. confval:: variant_name
|
||||||
|
|
||||||
|
Some workloads use variants of APK files, this is usually the case with web browser APK files, these work
|
||||||
|
in exactly the same way as the version, the variant of the apk
|
||||||
|
|
@@ -1,6 +1,454 @@
|
|||||||
=================================
|
=================================
|
||||||
What's New in Workload Automation
|
What's New in Workload Automation
|
||||||
=================================
|
=================================
|
||||||
|
-------------
|
||||||
|
Version 2.6.0
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. note:: Users who are currently using the GitHub master version of WA should
|
||||||
|
uninstall the existing version before upgrading to avoid potential issues.
|
||||||
|
|
||||||
|
Additions:
|
||||||
|
##########
|
||||||
|
|
||||||
|
Workloads
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``AdobeReader``: A workload that carries out following typical productivity
|
||||||
|
tasks. These include opening a file, performing various gestures and
|
||||||
|
zooms on screen and searching for a predefined set of strings.
|
||||||
|
- ``octaned8``: A workload to run the binary (non-browser) version of the JS
|
||||||
|
benchmark Octane.
|
||||||
|
- ``GooglePlayBooks``: A workload to perform standard productivity tasks with
|
||||||
|
Google Play Books. This workload performs various tasks, such as searching
|
||||||
|
for a book title online, browsing through a book, adding and removing notes,
|
||||||
|
word searching, and querying information about the book.
|
||||||
|
- ``GooglePhotos``: A workload to perform standard productivity tasks with
|
||||||
|
Google Photos. Carries out various tasks, such as browsing images,
|
||||||
|
performing zooms, and post-processing the image.
|
||||||
|
- ``GoogleSlides``: Carries out various tasks, such as creating a new
|
||||||
|
presentation, adding text, images, and shapes, as well as basic editing and
|
||||||
|
playing a slideshow.
|
||||||
|
- ``Youtube``: The workload plays a video, determined by the ``video_source``
|
||||||
|
parameter. While the video is playing, some common actions such as video
|
||||||
|
seeking, pausing playback and navigating the comments section are performed.
|
||||||
|
- ``Skype``: Replacement for the ``skypevideo`` workload. Logs into Skype
|
||||||
|
and initiates a voice or video call with a contact.
|
||||||
|
|
||||||
|
Framework
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``AndroidUxPerfWorkload``: Added a new workload class to encapsulate
|
||||||
|
functionality common to all uxperf workloads.
|
||||||
|
- ``UxPerfUiAutomation``: Added class which contains methods specific to
|
||||||
|
UX performance
|
||||||
|
testing.
|
||||||
|
- ``get-assets``: Added new script and command to retrieve external assets
|
||||||
|
for workloads
|
||||||
|
|
||||||
|
Results Processors
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
- ``uxperf``: Parses device logcat for `UX_PERF` markers to produce performance
|
||||||
|
metrics for workload actions using specified instrumentation.
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- ``State Detection``: Added feature to use visual state detection to
|
||||||
|
verify the state of a workload after setup and run.
|
||||||
|
|
||||||
|
|
||||||
|
Fixes/Improvements:
|
||||||
|
###################
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
- ``Revent``: Added file structure to the documentation.
|
||||||
|
- Clarified documentation regarding binary dependencies.
|
||||||
|
- Updated documentation with ``create`` and ``get-assets`` commands.
|
||||||
|
|
||||||
|
Instruments
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
- ``sysfs_extractor``: Fixed error when `tar.gz` file already existed on device,
|
||||||
|
now overwrites.
|
||||||
|
- ``cpufreq``: Fixed error when `tar.gz` file already existed on device, now
|
||||||
|
overwrites.
|
||||||
|
- ``file-poller``:
|
||||||
|
- Improved csv output.
|
||||||
|
- Added error checking and reporting.
|
||||||
|
- Changed ``files`` to be a mandatory parameter.
|
||||||
|
- ``fps``:
|
||||||
|
- Added a new parameter to fps instrument to specify the time period between
|
||||||
|
calls to ``dumpsys SurfaceFlinger --latency`` when collecting frame data.
|
||||||
|
- Added gfxinfo methods to obtain fps stats. Auto detects and uses appropriate
|
||||||
|
method via android version of device.
|
||||||
|
- Fixed issue with regex.
|
||||||
|
- Now handles empty frames correctly.
|
||||||
|
- ``energy_model``: Ensures that the ``ui`` runtime parameter is only set for
|
||||||
|
ChromeOS devices.
|
||||||
|
- ``ftrace``: Added support to handle traces collected by both WA and devlib.
|
||||||
|
- ``Perf``: Updated 32bit binary file for little endian devices.
|
||||||
|
|
||||||
|
Resource Getters
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
- ``http_getter``: Now used to try and find executables files from a
|
||||||
|
provided ``remove_assets_url``.
|
||||||
|
|
||||||
|
Result Processors
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
- ``cpu_states``: Fixes using stand-alone script with timeline option.
|
||||||
|
|
||||||
|
Workloads
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``antutu``: Fixed setting permissions of ``FINE_LOCATION`` on some devices.
|
||||||
|
- ``bbench`` Fixed handling of missing results.
|
||||||
|
- ``camerarecord``:
|
||||||
|
- Added frame stats collection through dumpsys gfxinfo.
|
||||||
|
- Added possibility to select slow_motion recording mode.
|
||||||
|
- ``Geekbench``:
|
||||||
|
- Fixed output file listing causing pull failure.
|
||||||
|
- Added support for Geekbench 4.
|
||||||
|
- ``recentfling``:
|
||||||
|
- Fixed issue when binaries were not uninstalled correctly.
|
||||||
|
- Scripts are now deployed via ``install()`` to ensure they are executable.
|
||||||
|
- Fixed handling of when a PID file is deleted before reaching processing
|
||||||
|
results stage.
|
||||||
|
- Added parameter to not start any apps before flinging.
|
||||||
|
- ``rt-app``: Added camera recorder simulation.
|
||||||
|
- ``sysbench``: Added arm64 binary.
|
||||||
|
- ``Vellamo``: Fixed capitalization in part of UIAutomation to prevent
|
||||||
|
potential issues.
|
||||||
|
- ``Spec2000``: Now uses WA deployed version of busybox.
|
||||||
|
- ``NetStat``: Updated to support new default logcat format in Android 6.
|
||||||
|
- ``Dex2oat``: Now uses root if available.
|
||||||
|
|
||||||
|
Framework
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``adb_shell``:
|
||||||
|
- Fixed issue when using single quoted command with ``adb_shell``.
|
||||||
|
- Correctly forward stderror to the caller for newer version of adb.
|
||||||
|
- ``revent``
|
||||||
|
- Added ``-S`` argument to "record" command to automatically record a
|
||||||
|
screen capture after a recording is completed.
|
||||||
|
- Fixed issue with multiple iterations of a revent workload.
|
||||||
|
- Added ``-s`` option to executable to allow waiting on stdin.
|
||||||
|
- Removed timeout in command as ``-s`` is specified.
|
||||||
|
- Revent recordings can now be parsed and used within WA.
|
||||||
|
- Fixed issue when some recordings wouldn't be retrieved correctly.
|
||||||
|
- Timeout is now based on recording duration.
|
||||||
|
- Added `magic` and file version to revent files. Revent files should now
|
||||||
|
start with ``REVENT`` followed by the file format version.
|
||||||
|
- Added support for gamepad recording. This type of recording contains
|
||||||
|
only the events from a gamepad device (which is automatically
|
||||||
|
identified).
|
||||||
|
- A ``mode`` field has been added to the recording format to help
|
||||||
|
distinguish between the normal and gamepad recording types.
|
||||||
|
- Added ``-g`` option to ``record`` command to expose the gamepad recording
|
||||||
|
mode.
|
||||||
|
- The structure of revent code has undergone a major overhaul to improve
|
||||||
|
maintainability and robustness.
|
||||||
|
- More detailed ``info`` command output.
|
||||||
|
- Updated Makefile to support debug/production builds.
|
||||||
|
- ``Android API``: Upgraded Android API level from 17 to 18.
|
||||||
|
- ``uiautomator``: The window hierarchy is now dumped to a file when WA fails
|
||||||
|
on android devices.
|
||||||
|
- ``AndroidDevice``:
|
||||||
|
- Added support for downgrading when installing an APK.
|
||||||
|
- Added a ``broadcast_media_mounted`` method to force a re-index of the
|
||||||
|
mediaserver cache for a specified directory.
|
||||||
|
- Now correctly handles ``None`` output for ``get_pids_of()`` when there are no
|
||||||
|
running processes with the specified name.
|
||||||
|
- Renamed the capture method from ``capture_view_hierachy`` to
|
||||||
|
``capture_ui_hierarchy``.
|
||||||
|
- Changed the file extension of the capture file to ``.uix``
|
||||||
|
- Added ``-rf`` to delete_files to be consistent with ``LinuxDevice``.
|
||||||
|
- ``LinuxDevice``: Now ensures output from both stdout and etderr is propagated in
|
||||||
|
the event of a DeviceError.
|
||||||
|
- ``APKWorkload``:
|
||||||
|
- Now ensure APKs are replaced properly when reinstalling.
|
||||||
|
- Now checks APK version and ABI when installing.
|
||||||
|
- Fixed error on some devices when trying to grant permissions that were
|
||||||
|
already granted.
|
||||||
|
- Fixed some permissions not being granted.
|
||||||
|
- Now allows disabling the main activity launch in setup (required for some
|
||||||
|
apps).
|
||||||
|
- Added parameter to clear data on reset (default behaviour unchanged).
|
||||||
|
- Ignores exception for non-fatal permission grant failure.
|
||||||
|
- Fixed issue of multiple versions of the same workload failing to find their APK.
|
||||||
|
- Added method to ensure a valid apk version is used within a workload.
|
||||||
|
- Updated how APK resolution is performed to maximise likelihood of
|
||||||
|
a workload running.
|
||||||
|
- When ``check_apk`` is ``True`` will prefer host APK and if no suitable APK
|
||||||
|
is found, will use target APK if the correct version is present. When ``False``
|
||||||
|
will prefer target apk if it is a valid version otherwise will fallback to
|
||||||
|
host APK.
|
||||||
|
- ``RunConfiguration``: Fixed disabling of instruments in workload specs.
|
||||||
|
- ``Devices``:
|
||||||
|
- Added network connectivity check for devices.
|
||||||
|
- Subclasses can now set ``requires_network`` to ``True`` and network
|
||||||
|
connectivity check will be performed during ``setup()``.
|
||||||
|
- ``Workloads``:
|
||||||
|
- Added network check methods.
|
||||||
|
- Fixed versions to be backwards compatible.
|
||||||
|
- Updated workload versions to match APK files.
|
||||||
|
- Fixed issues with calling super.
|
||||||
|
- ``Assets``: Added script to retrieve external assets for workloads.
|
||||||
|
- ``Execution``: Added a ``clean_up`` global config option to delete WA files from
|
||||||
|
devices.
|
||||||
|
- ``Runner``: No longer takes a screenshot or dump of UI hierarchy for some errors when
|
||||||
|
unnecessary, e.g. host errors.
|
||||||
|
- ``core``: Constraints and allowed values are now checked when set instead of
|
||||||
|
when validating.
|
||||||
|
- ``FpsProcessor``:
|
||||||
|
- Added requirement on ``filtered_vsyncs_to_compose`` for ``total_vsync metric``.
|
||||||
|
- Removed misleading comment in class description.
|
||||||
|
- ``BaseUiAutomation``: Added new Marker API so workloads generate start and end
|
||||||
|
markers with a string name.
|
||||||
|
- ``AndroidUiAutoBenchmark``: Automatically checks for known package versions
|
||||||
|
that don't work well with AndroidUiAutoBenchmark workloads.
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Updated setup.py url to be a valid URI.
|
||||||
|
- Fixed workload name in big.Little sample agenda.
|
||||||
|
|
||||||
|
Incompatible changes
|
||||||
|
####################
|
||||||
|
|
||||||
|
Framework
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``check_abi``: Now renamed to ``exact_abi``, is used to ensure that if enabled,
|
||||||
|
only an apk containing no native code or code designed for the devices primary
|
||||||
|
abi is use.
|
||||||
|
- ``AndroidDevice``: Renamed ``supported_eabis`` property to ``supported_abis``
|
||||||
|
to be consistent with linux devices.
|
||||||
|
|
||||||
|
Workloads
|
||||||
|
~~~~~~~~~~
|
||||||
|
- ``skypevideo``: Workload removed and replaced with ``skype`` workload.
|
||||||
|
|
||||||
|
-------------
|
||||||
|
Version 2.5.0
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Additions:
|
||||||
|
##########
|
||||||
|
|
||||||
|
Instruments
|
||||||
|
~~~~~~~~~~~
|
||||||
|
- ``servo_power``: Added support for chromebook servo boards.
|
||||||
|
- ``file_poller``: polls files and outputs a CSV of their values over time.
|
||||||
|
- ``systrace``: The Systrace tool helps analyze the performance of your
|
||||||
|
application by capturing and displaying execution times of your applications
|
||||||
|
processes and other Android system processes.
|
||||||
|
|
||||||
|
Workloads
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``blogbench``: Blogbench is a portable filesystem benchmark that tries to
|
||||||
|
reproduce the load of a real-world busy file server.
|
||||||
|
- ``stress-ng``: Designed to exercise various physical subsystems of a computer
|
||||||
|
as well as the various operating system kernel interfaces.
|
||||||
|
- ``hwuitest``: Uses hwuitest from AOSP to test rendering latency on Android
|
||||||
|
devices.
|
||||||
|
- ``recentfling``: Tests UI jank on android devices.
|
||||||
|
- ``apklaunch``: installs and runs an arbitrary apk file.
|
||||||
|
- ``googlemap``: Launches Google Maps and replays previously recorded
|
||||||
|
interactions.
|
||||||
|
|
||||||
|
Framework
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``wlauto.utils.misc``: Added ``memoised`` function decorator that allows
|
||||||
|
caching of previous function/method call results.
|
||||||
|
- Added new ``Device`` APIs:
|
||||||
|
- ``lsmod``: lists kernel modules
|
||||||
|
- ``insmod``: inserts a kernel module from a ``.ko`` file on the host.
|
||||||
|
- ``get_binary_path``: Checks ``binary_directory`` for the wanted binary,
|
||||||
|
if it is not found there it will try to use ``which``
|
||||||
|
- ``install_if_needed``: Will only install a binary if it is not already
|
||||||
|
on the target.
|
||||||
|
- ``get_device_model``: Gets the model of the device.
|
||||||
|
- ``wlauto.core.execution.ExecutionContext``:
|
||||||
|
- ``add_classfiers``: Allows adding a classfier to all metrics for the
|
||||||
|
current result.
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Commands:
|
||||||
|
- ``record``: Simplifies recording revent files.
|
||||||
|
- ``replay``: Plays back revent files.
|
||||||
|
|
||||||
|
Fixes/Improvements:
|
||||||
|
###################
|
||||||
|
|
||||||
|
Devices
|
||||||
|
~~~~~~~
|
||||||
|
- ``juno``:
|
||||||
|
- Fixed ``bootargs`` parameter not being passed to ``_boot_via_uboot``.
|
||||||
|
- Removed default ``bootargs``
|
||||||
|
- ``gem5_linux``:
|
||||||
|
- Added ``login_prompt`` and ``login_password_prompt`` parameters.
|
||||||
|
- ``generic_linux``: ABI is now read from the target device.
|
||||||
|
|
||||||
|
Instruments
|
||||||
|
~~~~~~~~~~~
|
||||||
|
- ``trace-cmd``:
|
||||||
|
- Added the ability to report the binary trace on the target device,
|
||||||
|
removing the need for ``trace-cmd`` binary to be present on the host.
|
||||||
|
- Updated to handle messages that the trace for a CPU is empty.
|
||||||
|
- Made timeout for pulling trace 1 minute at minimum.
|
||||||
|
- ``perf``: per-cpu statistics now get added as metrics to the results (with a
|
||||||
|
classifier used to identify the cpu).
|
||||||
|
- ``daq``:
|
||||||
|
- Fixed bug where an exception would be raised if ``merge_channels=False``
|
||||||
|
- No longer allows duplicate channel labels
|
||||||
|
- ``juno_energy``:
|
||||||
|
- Summary metrics are now calculated from the contents of ``energy.csv`` and
|
||||||
|
added to the overall results.
|
||||||
|
- Added a ``strict`` parameter. When this is set to ``False`` the device
|
||||||
|
check during validation is omitted.
|
||||||
|
- ``sysfs_extractor``: tar and gzip are now performed separately to solve
|
||||||
|
permission issues.
|
||||||
|
- ``fps``:
|
||||||
|
- Now only checks for crashed content if ``crash_check`` is ``True``.
|
||||||
|
- Can now process multiple ``view`` attributes.
|
||||||
|
- ``hwmon``: Sensor naming fixed, they are also now added as result classifiers
|
||||||
|
|
||||||
|
Resource Getters
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
- ``extension_asset``: Now picks up the path to the mounted filer from the
|
||||||
|
``remote_assets_path`` global setting.
|
||||||
|
|
||||||
|
Result Processors
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
- ``cpustates``:
|
||||||
|
- Added the ability to configure how a missing ``START`` marker in the trace
|
||||||
|
is handled.
|
||||||
|
- Now raises a warning when there is a ``START`` marker in the trace but no
|
||||||
|
``STOP`` marker.
|
||||||
|
- Exceptions in PowerStateProcessor no longer stop the processing of the
|
||||||
|
rest of the trace.
|
||||||
|
- Now ensures a known initial state by nudging each CPU to bring it out of
|
||||||
|
idle and writing starting CPU frequencies to the trace.
|
||||||
|
- Added the ability to create a CPU utilisation timeline.
|
||||||
|
- Fixed issues with getting frequencies of hotplugged CPUs
|
||||||
|
- ``csv``: Zero-value classifieres are no longer converted to an empty entry.
|
||||||
|
- ``ipynb_exporter``: Default template no longer shows a blank plot for
|
||||||
|
workloads without ``summary_metrics``
|
||||||
|
|
||||||
|
Workloads
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``vellamo``:
|
||||||
|
- Added support for v3.2.4.
|
||||||
|
- Fixed getting values from logcat.
|
||||||
|
- ``cameracapture``: Updated to work with Android M+.
|
||||||
|
- ``camerarecord``: Updated to work with Android M+.
|
||||||
|
- ``lmbench``:
|
||||||
|
- Added the output file as an artifact.
|
||||||
|
- Added taskset support
|
||||||
|
- ``antutu`` - Added support for v6.0.1
|
||||||
|
- ``ebizzy``: Fixed use of ``os.path`` to ``self.device.path``.
|
||||||
|
- ``bbench``: Fixed browser crashes & permissions issues on android M+.
|
||||||
|
- ``geekbench``:
|
||||||
|
- Added check whether device is rooted.
|
||||||
|
- ``manual``: Now only uses logcat on Android devices.
|
||||||
|
- ``applaunch``:
|
||||||
|
- Fixed ``cleanup`` not getting forwarded to script.
|
||||||
|
- Added the ability to stress IO during app launch.
|
||||||
|
- ``dhrystone``: Now uses WA's resource resolution to find it's binary so it
|
||||||
|
uses the correct ABI.
|
||||||
|
- ``glbench``: Updated for new logcat formatting.
|
||||||
|
|
||||||
|
Framework
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``ReventWorkload``:
|
||||||
|
- Now kills all revent instances on teardown.
|
||||||
|
- Device model name is now used when searching for revent files, falling back
|
||||||
|
to WA device name.
|
||||||
|
- ``BaseLinuxDevice``:
|
||||||
|
- ``killall`` will now run as root by default if the device
|
||||||
|
is rooted.
|
||||||
|
- ``list_file_systems`` now handles blank lines.
|
||||||
|
- All binaries are now installed into ``binaries_directory`` this allows..
|
||||||
|
- Busybox is now deployed on non-root devices.
|
||||||
|
- gzipped property files are no zcat'ed
|
||||||
|
- ``LinuxDevice``:
|
||||||
|
- ``kick_off`` no longer requires root.
|
||||||
|
- ``kick_off`` will now run as root by default if the device is rooted.
|
||||||
|
- No longer raises an exception if a connection was dropped during a reboot.
|
||||||
|
- Added a delay before polling for a connection to avoid re-connecting to a
|
||||||
|
device that is still in the process of rebooting.
|
||||||
|
- ``wlauto.utils.types``: ``list_or_string`` now ensures that elements of a list
|
||||||
|
are strings.
|
||||||
|
- ``AndroidDevice``:
|
||||||
|
- ``kick_off`` no longer requires root.
|
||||||
|
- Build props are now gathered via ``getprop`` rather than trying to parse
|
||||||
|
build.prop directly.
|
||||||
|
- WA now pushes its own ``sqlite3`` binary.
|
||||||
|
- Now uses ``content`` instead of ``settings`` to get ``ANDROID_ID``
|
||||||
|
- ``swipe_to_unlock`` parameter is now actually used. It has been changed to
|
||||||
|
take a direction to accomodate various devices.
|
||||||
|
- ``ensure_screen_is_on`` will now also unlock the screen if swipe_to_unlock
|
||||||
|
is set.
|
||||||
|
- Fixed use of variables in as_root=True commands.
|
||||||
|
- ``get_pids_of`` now used ``busybox grep`` since as of Android M+ ps cannot
|
||||||
|
filter by process name anymore.
|
||||||
|
- Fixed installing APK files with whitespace in their path/name.
|
||||||
|
- ``adb_shell``:
|
||||||
|
- Fixed handling of line breaks at the end of command output.
|
||||||
|
- Newline separator is now detected from the target.
|
||||||
|
- As of ADB v1.0.35, ADB returns the return code of the command run. WA now
|
||||||
|
handles this correctly.
|
||||||
|
- ``ApkWorkload``:
|
||||||
|
- Now attempts to grant all runtime permissions for devices on Android M+.
|
||||||
|
- Can now launch packages that don't have a launch activity defined.
|
||||||
|
- Package version is now added to results as a classifier.
|
||||||
|
- Now clears app data if an uninstall failed to ensure it starts from a known
|
||||||
|
state.
|
||||||
|
- ``wlauto.utils.ipython``: Updated to work with ipython v5.
|
||||||
|
- ``Gem5Device``:
|
||||||
|
- Added support for deploying the ``m5`` binary.
|
||||||
|
- No longer waits for the boot animation to finish if it has been disabled.
|
||||||
|
- Fixed runtime error caused by lack of kwargs.
|
||||||
|
- No longer depends on ``busybox``.
|
||||||
|
- Split out commands to resize shell to ``resize_shell``.
|
||||||
|
- Now tries to connect to the shell up to 10 times.
|
||||||
|
- No longer renames gzipped files.
|
||||||
|
- Agendas:
|
||||||
|
- Now errors when an agenda key is empty.
|
||||||
|
- ``wlauto.core.execution.RunInfo``: ``run_name`` will now default to
|
||||||
|
``{output_folder}_{date}_{time}``.
|
||||||
|
- Extensions:
|
||||||
|
- Two different parameters can now have the same global alias as long as they
|
||||||
|
their types match.
|
||||||
|
- You can no longer ``override`` parameters that are defined at the same
|
||||||
|
level.
|
||||||
|
- ``wlauto.core.entry_point``: Now gives a better error when a config file
|
||||||
|
doesn't exist.
|
||||||
|
- ``wlauto.utils.misc``: Added ``aarch64`` to list for arm64 ABI.
|
||||||
|
- ``wlauto.core.resolver``: Now shows what version was being search for when a
|
||||||
|
resource is not found.
|
||||||
|
- Will no longer start instruments ect. if a run has no workload specs.
|
||||||
|
- ``wlauto.utils.uboot``: Now detects uboot version to use correct line endings.
|
||||||
|
- ``wlauto.utils.trace_cmd``: Added a parser for sched_switch events.
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Updated to pylint v1.5.1
|
||||||
|
- Rebuilt ``busybox`` binaries to prefer built-in applets over system binaries.
|
||||||
|
- ``BaseUiAutomation``: Added functions for checking version strings.
|
||||||
|
|
||||||
|
Incompatible changes
|
||||||
|
####################
|
||||||
|
|
||||||
|
Instruments
|
||||||
|
~~~~~~~~~~~
|
||||||
|
- ``apk_version``: Removed, use result classifiers instead.
|
||||||
|
|
||||||
|
Framework
|
||||||
|
~~~~~~~~~
|
||||||
|
- ``BaseLinuxDevice``: Removed ``is_installed`` use ``install_if_needed`` and
|
||||||
|
``get_binary_path`` instead.
|
||||||
|
- ``LinuxDevice``: Removed ``has_root`` method, use ``is_rooted`` instead.
|
||||||
|
- ``AndroidDevice``: ``swipe_to_unlock`` method replaced with
|
||||||
|
``perform_unlock_swipe``.
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
Version 2.4.0
|
Version 2.4.0
|
||||||
-------------
|
-------------
|
||||||
|
@@ -64,7 +64,7 @@ you might want to change are outlined below.
|
|||||||
advanced WA functionality (like setting of core-related runtime parameters
|
advanced WA functionality (like setting of core-related runtime parameters
|
||||||
such as governors, frequencies, etc). ``core_names`` should be a list of
|
such as governors, frequencies, etc). ``core_names`` should be a list of
|
||||||
core names matching the order in which they are exposed in sysfs. For
|
core names matching the order in which they are exposed in sysfs. For
|
||||||
example, ARM TC2 SoC is a 2x3 big.LITTLE system; it's core_names would be
|
example, ARM TC2 SoC is a 2x3 big.LITTLE system; its core_names would be
|
||||||
``['a7', 'a7', 'a7', 'a15', 'a15']``, indicating that cpu0-cpu2 in cpufreq
|
``['a7', 'a7', 'a7', 'a15', 'a15']``, indicating that cpu0-cpu2 in cpufreq
|
||||||
sysfs structure are A7's and cpu3 and cpu4 are A15's.
|
sysfs structure are A7's and cpu3 and cpu4 are A15's.
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -59,6 +59,11 @@ usually the best bet.
|
|||||||
Optionally (but recommended), you should also set ``ANDROID_HOME`` to point to
|
Optionally (but recommended), you should also set ``ANDROID_HOME`` to point to
|
||||||
the install location of the SDK (i.e. ``<path_to_android_sdk>/sdk``).
|
the install location of the SDK (i.e. ``<path_to_android_sdk>/sdk``).
|
||||||
|
|
||||||
|
.. note:: You may need to install 32-bit compatibility libararies for the SDK
|
||||||
|
to work properly. On Ubuntu you need to run::
|
||||||
|
|
||||||
|
sudo apt-get install lib32stdc++6 lib32z1
|
||||||
|
|
||||||
|
|
||||||
Python
|
Python
|
||||||
------
|
------
|
||||||
@@ -87,7 +92,7 @@ similar distributions, this may be done with APT::
|
|||||||
If you do run into this issue after already installing some packages,
|
If you do run into this issue after already installing some packages,
|
||||||
you can resolve it by running ::
|
you can resolve it by running ::
|
||||||
|
|
||||||
sudo chmod -R a+r /usr/local/lib/python2.7/dist-packagessudo
|
sudo chmod -R a+r /usr/local/lib/python2.7/dist-packagessudo
|
||||||
find /usr/local/lib/python2.7/dist-packages -type d -exec chmod a+x {} \;
|
find /usr/local/lib/python2.7/dist-packages -type d -exec chmod a+x {} \;
|
||||||
|
|
||||||
(The paths above will work for Ubuntu; they may need to be adjusted
|
(The paths above will work for Ubuntu; they may need to be adjusted
|
||||||
@@ -187,10 +192,28 @@ version $version".
|
|||||||
Some WA extensions have additional dependencies that need to be
|
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
|
||||||
---------
|
---------
|
||||||
@@ -307,7 +330,7 @@ that location.
|
|||||||
|
|
||||||
If you have installed Workload Automation via ``pip`` and wish to remove it, run this command to
|
If you have installed Workload Automation via ``pip`` and wish to remove it, run this command to
|
||||||
uninstall it::
|
uninstall it::
|
||||||
|
|
||||||
sudo -H pip uninstall wlauto
|
sudo -H pip uninstall wlauto
|
||||||
|
|
||||||
.. Note:: This will *not* remove any user configuration (e.g. the ~/.workload_automation directory)
|
.. Note:: This will *not* remove any user configuration (e.g. the ~/.workload_automation directory)
|
||||||
@@ -317,5 +340,5 @@ uninstall it::
|
|||||||
====================
|
====================
|
||||||
|
|
||||||
To upgrade Workload Automation to the latest version via ``pip``, run::
|
To upgrade Workload Automation to the latest version via ``pip``, run::
|
||||||
|
|
||||||
sudo -H pip install --upgrade --no-deps wlauto
|
sudo -H pip install --upgrade --no-deps wlauto
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
.. _invocation:
|
.. _invocation:
|
||||||
|
.. highlight:: none
|
||||||
|
|
||||||
========
|
========
|
||||||
Commands
|
Commands
|
||||||
========
|
========
|
||||||
|
|
||||||
Installing the wlauto package will add ``wa`` command to your system,
|
Installing the wlauto package will add ``wa`` command to your system,
|
||||||
which you can run from anywhere. This has a number of sub-commands, which can
|
which you can run from anywhere. This has a number of sub-commands, which can
|
||||||
be viewed by executing ::
|
be viewed by executing ::
|
||||||
|
|
||||||
wa -h
|
wa -h
|
||||||
@@ -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
|
||||||
@@ -131,5 +196,64 @@ will produce something like ::
|
|||||||
- Results displayed in Iterations per second
|
- Results displayed in Iterations per second
|
||||||
- Detailed log file for comprehensive engineering analysis
|
- Detailed log file for comprehensive engineering analysis
|
||||||
|
|
||||||
|
.. _record-command:
|
||||||
|
|
||||||
|
record
|
||||||
|
------
|
||||||
|
|
||||||
|
This command simplifies the process of recording an revent file. It
|
||||||
|
will automatically deploy revent and even has the option of automatically
|
||||||
|
opening apps. WA uses two parts to the names of revent recordings in the
|
||||||
|
format, {device_name}.{suffix}.revent. - device_name can either be specified
|
||||||
|
manually with the ``-d`` argument or it can be automatically determined. On
|
||||||
|
Android device it will be obtained from ``build.prop``, on Linux devices it is
|
||||||
|
obtained from ``/proc/device-tree/model``. - suffix is used by WA to determine
|
||||||
|
which part of the app execution the recording is for, currently these are
|
||||||
|
either ``setup`` or ``run``. This should be specified with the ``-s``
|
||||||
|
argument. The full set of options for this command are::
|
||||||
|
|
||||||
|
usage: wa record [-h] [-c CONFIG] [-v] [--debug] [--version] [-d DEVICE]
|
||||||
|
[-s SUFFIX] [-o OUTPUT] [-p PACKAGE] [-g] [-C]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-c CONFIG, --config CONFIG
|
||||||
|
specify an additional config.py
|
||||||
|
-v, --verbose The scripts will produce verbose output.
|
||||||
|
--debug Enable debug mode. Note: this implies --verbose.
|
||||||
|
--version show program's version number and exit
|
||||||
|
-d DEVICE, --device DEVICE
|
||||||
|
The name of the device
|
||||||
|
-s SUFFIX, --suffix SUFFIX
|
||||||
|
The suffix of the revent file, e.g. ``setup``
|
||||||
|
-o OUTPUT, --output OUTPUT
|
||||||
|
Directory to save the recording in
|
||||||
|
-p PACKAGE, --package PACKAGE
|
||||||
|
Package to launch before recording
|
||||||
|
-g, --gamepad Record from a gamepad rather than all devices.
|
||||||
|
-C, --clear Clear app cache before launching it
|
||||||
|
|
||||||
|
.. _replay-command:
|
||||||
|
|
||||||
|
replay
|
||||||
|
------
|
||||||
|
|
||||||
|
Along side ``record`` wa also has a command to playback recorded revent files.
|
||||||
|
It behaves very similar to the ``record`` command taking many of the same options::
|
||||||
|
|
||||||
|
usage: wa replay [-h] [-c CONFIG] [-v] [--debug] [--version] [-p PACKAGE] [-C]
|
||||||
|
revent
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
revent The name of the file to replay
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-c CONFIG, --config CONFIG
|
||||||
|
specify an additional config.py
|
||||||
|
-v, --verbose The scripts will produce verbose output.
|
||||||
|
--debug Enable debug mode. Note: this implies --verbose.
|
||||||
|
--version show program's version number and exit
|
||||||
|
-p PACKAGE, --package PACKAGE
|
||||||
|
Package to launch before recording
|
||||||
|
-C, --clear Clear app cache before launching it
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
.. _resources:
|
||||||
|
|
||||||
Dynamic Resource Resolution
|
Dynamic Resource Resolution
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
@@ -7,10 +9,10 @@ The idea is to decouple resource identification from resource discovery.
|
|||||||
Workloads/instruments/devices/etc state *what* resources they need, and not
|
Workloads/instruments/devices/etc state *what* resources they need, and not
|
||||||
*where* to look for them -- this instead is left to the resource resolver that
|
*where* to look for them -- this instead is left to the resource resolver that
|
||||||
is now part of the execution context. The actual discovery of resources is
|
is now part of the execution context. The actual discovery of resources is
|
||||||
performed by resource getters that are registered with the resolver.
|
performed by resource getters that are registered with the resolver.
|
||||||
|
|
||||||
A resource type is defined by a subclass of
|
A resource type is defined by a subclass of
|
||||||
:class:`wlauto.core.resource.Resource`. An instance of this class describes a
|
:class:`wlauto.core.resource.Resource`. An instance of this class describes a
|
||||||
resource that is to be obtained. At minimum, a ``Resource`` instance has an
|
resource that is to be obtained. At minimum, a ``Resource`` instance has an
|
||||||
owner (which is typically the object that is looking for the resource), but
|
owner (which is typically the object that is looking for the resource), but
|
||||||
specific resource types may define other parameters that describe an instance of
|
specific resource types may define other parameters that describe an instance of
|
||||||
|
@@ -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
|
||||||
@@ -20,28 +23,44 @@ to Android UI Automator for providing automation for workloads. ::
|
|||||||
Recording
|
Recording
|
||||||
---------
|
---------
|
||||||
|
|
||||||
To record, transfer the revent binary to the device, then invoke ``revent
|
WA features a ``record`` command that will automatically deploy and start
|
||||||
record``, giving it the time (in seconds) you want to record for, and the
|
revent on the target device::
|
||||||
file you want to record to (WA expects these files to have .revent
|
|
||||||
extension)::
|
|
||||||
|
|
||||||
host$ adb push revent /data/local/revent
|
wa record
|
||||||
host$ adb shell
|
INFO Connecting to device...
|
||||||
device# cd /data/local
|
INFO Press Enter when you are ready to record...
|
||||||
device# ./revent record 1000 my_recording.revent
|
[Pressed Enter]
|
||||||
|
INFO Press Enter when you have finished recording...
|
||||||
|
[Pressed Enter]
|
||||||
|
INFO Pulling files from device
|
||||||
|
|
||||||
|
Once started, you will need to get the target device ready to record (e.g.
|
||||||
|
unlock screen, navigate menus and launch an app) then press ``ENTER``.
|
||||||
|
The recording has now started and button presses, taps, etc you perform on
|
||||||
|
the device will go into the .revent file. To stop the recording simply press
|
||||||
|
``ENTER`` again.
|
||||||
|
|
||||||
|
Once you have finished recording the revent file will be pulled from the device
|
||||||
|
to the current directory. It will be named ``{device_model}.revent``. When
|
||||||
|
recording revent files for a ``GameWorkload`` you can use the ``-s`` option to
|
||||||
|
add ``run`` or ``setup`` suffixes.
|
||||||
|
|
||||||
|
From version 2.6 of WA onwards, a "gamepad" recording mode is also supported.
|
||||||
|
This mode requires a gamepad to be connected to the device when recoridng, but
|
||||||
|
the recordings produced in this mode should be portable across devices.
|
||||||
|
|
||||||
|
For more information run please read :ref:`record-command`
|
||||||
|
|
||||||
The recording has now started and button presses, taps, etc you perform on the
|
|
||||||
device will go into the .revent file. The recording will stop after the
|
|
||||||
specified time period, and you can also stop it by hitting return in the adb
|
|
||||||
shell.
|
|
||||||
|
|
||||||
Replaying
|
Replaying
|
||||||
---------
|
---------
|
||||||
|
|
||||||
To replay a recorded file, run ``revent replay`` on the device, giving it the
|
To replay a recorded file, run ``wa replay``, giving it the file you want to
|
||||||
file you want to replay::
|
replay::
|
||||||
|
|
||||||
device# ./revent replay my_recording.revent
|
wa replay my_recording.revent
|
||||||
|
|
||||||
|
For more information run please read :ref:`replay-command`
|
||||||
|
|
||||||
|
|
||||||
Using revent With Workloads
|
Using revent With Workloads
|
||||||
@@ -95,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)
|
||||||
|
@@ -11,7 +11,7 @@ interesting of these are
|
|||||||
can be benchmarks, high-level use cases, or pretty much anything else.
|
can be benchmarks, high-level use cases, or pretty much anything else.
|
||||||
:devices: These are interfaces to the physical devices (development boards or end-user
|
:devices: These are interfaces to the physical devices (development boards or end-user
|
||||||
devices, such as smartphones) that use cases run on. Typically each model of a
|
devices, such as smartphones) that use cases run on. Typically each model of a
|
||||||
physical device would require it's own interface class (though some functionality
|
physical device would require its own interface class (though some functionality
|
||||||
may be reused by subclassing from an existing base).
|
may be reused by subclassing from an existing base).
|
||||||
:instruments: Instruments allow collecting additional data from workload execution (e.g.
|
:instruments: Instruments allow collecting additional data from workload execution (e.g.
|
||||||
system traces). Instruments are not specific to a particular Workload. Instruments
|
system traces). Instruments are not specific to a particular Workload. Instruments
|
||||||
@@ -31,7 +31,7 @@ Extension Basics
|
|||||||
================
|
================
|
||||||
|
|
||||||
This sub-section covers things common to implementing extensions of all types.
|
This sub-section covers things common to implementing extensions of all types.
|
||||||
It is recommended you familiarize yourself with the information here before
|
It is recommended you familiarize yourself with the information here before
|
||||||
proceeding onto guidance for specific extension types.
|
proceeding onto guidance for specific extension types.
|
||||||
|
|
||||||
To create an extension, you basically subclass an appropriate base class and them
|
To create an extension, you basically subclass an appropriate base class and them
|
||||||
@@ -41,22 +41,22 @@ The Context
|
|||||||
-----------
|
-----------
|
||||||
|
|
||||||
The majority of methods in extensions accept a context argument. This is an
|
The majority of methods in extensions accept a context argument. This is an
|
||||||
instance of :class:`wlauto.core.execution.ExecutionContext`. If contains
|
instance of :class:`wlauto.core.execution.ExecutionContext`. If contains
|
||||||
of information about current state of execution of WA and keeps track of things
|
of information about current state of execution of WA and keeps track of things
|
||||||
like which workload is currently running and the current iteration.
|
like which workload is currently running and the current iteration.
|
||||||
|
|
||||||
Notable attributes of the context are
|
Notable attributes of the context are
|
||||||
|
|
||||||
context.spec
|
context.spec
|
||||||
the current workload specification being executed. This is an
|
the current workload specification being executed. This is an
|
||||||
instance of :class:`wlauto.core.configuration.WorkloadRunSpec`
|
instance of :class:`wlauto.core.configuration.WorkloadRunSpec`
|
||||||
and defines the workload and the parameters under which it is
|
and defines the workload and the parameters under which it is
|
||||||
being executed.
|
being executed.
|
||||||
|
|
||||||
context.workload
|
context.workload
|
||||||
``Workload`` object that is currently being executed.
|
``Workload`` object that is currently being executed.
|
||||||
|
|
||||||
context.current_iteration
|
context.current_iteration
|
||||||
The current iteration of the spec that is being executed. Note that this
|
The current iteration of the spec that is being executed. Note that this
|
||||||
is the iteration for that spec, i.e. the number of times that spec has
|
is the iteration for that spec, i.e. the number of times that spec has
|
||||||
been run, *not* the total number of all iterations have been executed so
|
been run, *not* the total number of all iterations have been executed so
|
||||||
@@ -79,9 +79,9 @@ In addition to these, context also defines a few useful paths (see below).
|
|||||||
Paths
|
Paths
|
||||||
-----
|
-----
|
||||||
|
|
||||||
You should avoid using hard-coded absolute paths in your extensions whenever
|
You should avoid using hard-coded absolute paths in your extensions whenever
|
||||||
possible, as they make your code too dependent on a particular environment and
|
possible, as they make your code too dependent on a particular environment and
|
||||||
may mean having to make adjustments when moving to new (host and/or device)
|
may mean having to make adjustments when moving to new (host and/or device)
|
||||||
platforms. To help avoid hard-coded absolute paths, WA automation defines
|
platforms. To help avoid hard-coded absolute paths, WA automation defines
|
||||||
a number of standard locations. You should strive to define your paths relative
|
a number of standard locations. You should strive to define your paths relative
|
||||||
to one of those.
|
to one of those.
|
||||||
@@ -95,7 +95,7 @@ extension methods.
|
|||||||
context.run_output_directory
|
context.run_output_directory
|
||||||
This is the top-level output directory for all WA results (by default,
|
This is the top-level output directory for all WA results (by default,
|
||||||
this will be "wa_output" in the directory in which WA was invoked.
|
this will be "wa_output" in the directory in which WA was invoked.
|
||||||
|
|
||||||
context.output_directory
|
context.output_directory
|
||||||
This is the output directory for the current iteration. This will an
|
This is the output directory for the current iteration. This will an
|
||||||
iteration-specific subdirectory under the main results location. If
|
iteration-specific subdirectory under the main results location. If
|
||||||
@@ -104,7 +104,7 @@ context.output_directory
|
|||||||
|
|
||||||
context.host_working_directory
|
context.host_working_directory
|
||||||
This an addition location that may be used by extensions to store
|
This an addition location that may be used by extensions to store
|
||||||
non-iteration specific intermediate files (e.g. configuration).
|
non-iteration specific intermediate files (e.g. configuration).
|
||||||
|
|
||||||
Additionally, the global ``wlauto.settings`` object exposes on other location:
|
Additionally, the global ``wlauto.settings`` object exposes on other location:
|
||||||
|
|
||||||
@@ -132,12 +132,63 @@ device, the ``os.path`` modules should *not* be used for on-device path
|
|||||||
manipulation. Instead device has an equipment module exposed through
|
manipulation. Instead device has an equipment module exposed through
|
||||||
``device.path`` attribute. This has all the same attributes and behaves the
|
``device.path`` attribute. This has all the same attributes and behaves the
|
||||||
same way as ``os.path``, but is guaranteed to produce valid paths for the device,
|
same way as ``os.path``, but is guaranteed to produce valid paths for the device,
|
||||||
irrespective of the host's path notation.
|
irrespective of the host's path notation. For example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
result_file = self.device.path.join(self.device.working_directory, "result.txt")
|
||||||
|
self.command = "{} -a -b -c {}".format(target_binary, result_file)
|
||||||
|
|
||||||
.. note:: result processors, unlike workloads and instruments, do not have their
|
.. note:: result processors, unlike workloads and instruments, do not have their
|
||||||
own device attribute; however they can access the device through the
|
own device attribute; however they can access the device through the
|
||||||
context.
|
context.
|
||||||
|
|
||||||
|
Deploying executables to a device
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Some devices may have certain restrictions on where executable binaries may be
|
||||||
|
placed and how they should be invoked. To ensure your extension works with as
|
||||||
|
wide a range of devices as possible, you should use WA APIs for deploying and
|
||||||
|
invoking executables on a device, as outlined below.
|
||||||
|
|
||||||
|
As with other resources (see :ref:`resources`) , host-side paths to the exectuable
|
||||||
|
binary to be deployed should be obtained via the resource resolver. A special
|
||||||
|
resource type, ``Executable`` is used to identify a binary to be deployed.
|
||||||
|
This is simiar to the regular ``File`` resource, however it takes an additional
|
||||||
|
parameter that specifies the ABI for which executable was compiled.
|
||||||
|
|
||||||
|
In order for the binary to be obtained in this way, it must be stored in one of
|
||||||
|
the locations scanned by the resource resolver in a directry structure
|
||||||
|
``<root>/bin/<abi>/<binary>`` (where ``root`` is the base resource location to
|
||||||
|
be searched, e.g. ``~/.workload_automation/depencencies/<extension name>``, and
|
||||||
|
``<abi>`` is the ABI for which the exectuable has been compiled, as returned by
|
||||||
|
``self.device.abi``).
|
||||||
|
|
||||||
|
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``.
|
||||||
|
The latter will check a version of that binary has been perviously deployed by
|
||||||
|
WA and will not try to re-install.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from wlauto import Executable
|
||||||
|
|
||||||
|
host_binary = context.resolver.get(Executable(self, self.device.abi, 'some_binary'))
|
||||||
|
target_binary = self.device.install_if_needed(host_binary)
|
||||||
|
|
||||||
|
|
||||||
|
.. note:: Please also note that the check is done based solely on the binary name.
|
||||||
|
For more information please see: :func:`wlauto.common.linux.BaseLinuxDevice.install_if_needed`
|
||||||
|
|
||||||
|
Both of the above methods will return the path to the installed binary on the
|
||||||
|
device. The executable should be invoked *only* via that path; do **not** assume
|
||||||
|
that it will be in ``PATH`` on the target (or that the executable with the same
|
||||||
|
name in ``PATH`` is the version deployed by WA.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
self.command = "{} -a -b -c".format(target_binary)
|
||||||
|
self.device.execute(self.command)
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -188,11 +239,11 @@ mandatory
|
|||||||
and there really is no sensible default that could be given
|
and there really is no sensible default that could be given
|
||||||
(e.g. something like login credentials), should you consider
|
(e.g. something like login credentials), should you consider
|
||||||
making it mandatory.
|
making it mandatory.
|
||||||
|
|
||||||
constraint
|
constraint
|
||||||
This is an additional constraint to be enforced on the parameter beyond
|
This is an additional constraint to be enforced on the parameter beyond
|
||||||
its type or fixed allowed values set. This should be a predicate (a function
|
its type or fixed allowed values set. This should be a predicate (a function
|
||||||
that takes a single argument -- the user-supplied value -- and returns
|
that takes a single argument -- the user-supplied value -- and returns
|
||||||
a ``bool`` indicating whether the constraint has been satisfied).
|
a ``bool`` indicating whether the constraint has been satisfied).
|
||||||
|
|
||||||
override
|
override
|
||||||
@@ -201,7 +252,7 @@ override
|
|||||||
with the same name as already exists, you will get an error. If you do
|
with the same name as already exists, you will get an error. If you do
|
||||||
want to override a parameter from further up in the inheritance
|
want to override a parameter from further up in the inheritance
|
||||||
hierarchy, you can indicate that by setting ``override`` attribute to
|
hierarchy, you can indicate that by setting ``override`` attribute to
|
||||||
``True``.
|
``True``.
|
||||||
|
|
||||||
When overriding, you do not need to specify every other attribute of the
|
When overriding, you do not need to specify every other attribute of the
|
||||||
parameter, just the ones you what to override. Values for the rest will
|
parameter, just the ones you what to override. Values for the rest will
|
||||||
@@ -222,7 +273,7 @@ surrounding environment (e.g. that the device has been initialized).
|
|||||||
|
|
||||||
The contract for ``validate`` method is that it should raise an exception
|
The contract for ``validate`` method is that it should raise an exception
|
||||||
(either ``wlauto.exceptions.ConfigError`` or extension-specific exception type -- see
|
(either ``wlauto.exceptions.ConfigError`` or extension-specific exception type -- see
|
||||||
further on this page) if some validation condition has not, and cannot, been met.
|
further on this page) if some validation condition has not, and cannot, been met.
|
||||||
If the method returns without raising an exception, then the extension is in a
|
If the method returns without raising an exception, then the extension is in a
|
||||||
valid internal state.
|
valid internal state.
|
||||||
|
|
||||||
@@ -242,7 +293,7 @@ everything it is doing, so you shouldn't need to add much additional logging in
|
|||||||
your expansion's. But you might what to log additional information, e.g.
|
your expansion's. But you might what to log additional information, e.g.
|
||||||
what settings your extension is using, what it is doing on the host, etc.
|
what settings your extension is using, what it is doing on the host, etc.
|
||||||
Operations on the host will not normally be logged, so your extension should
|
Operations on the host will not normally be logged, so your extension should
|
||||||
definitely log what it is doing on the host. One situation in particular where
|
definitely log what it is doing on the host. One situation in particular where
|
||||||
you should add logging is before doing something that might take a significant amount
|
you should add logging is before doing something that might take a significant amount
|
||||||
of time, such as downloading a file.
|
of time, such as downloading a file.
|
||||||
|
|
||||||
@@ -259,7 +310,7 @@ Subsequent paragraphs (separated by blank lines) can then provide a more
|
|||||||
detailed description, including any limitations and setup instructions.
|
detailed description, including any limitations and setup instructions.
|
||||||
|
|
||||||
For parameters, the description is passed as an argument on creation. Please
|
For parameters, the description is passed as an argument on creation. Please
|
||||||
note that if ``default``, ``allowed_values``, or ``constraint``, are set in the
|
note that if ``default``, ``allowed_values``, or ``constraint``, are set in the
|
||||||
parameter, they do not need to be explicitly mentioned in the description (wa
|
parameter, they do not need to be explicitly mentioned in the description (wa
|
||||||
documentation utilities will automatically pull those). If the ``default`` is set
|
documentation utilities will automatically pull those). If the ``default`` is set
|
||||||
in ``validate`` or additional cross-parameter constraints exist, this *should*
|
in ``validate`` or additional cross-parameter constraints exist, this *should*
|
||||||
@@ -304,7 +355,7 @@ Utils
|
|||||||
Workload Automation defines a number of utilities collected under
|
Workload Automation defines a number of utilities collected under
|
||||||
:mod:`wlauto.utils` subpackage. These utilities were created to help with the
|
:mod:`wlauto.utils` subpackage. These utilities were created to help with the
|
||||||
implementation of the framework itself, but may be also be useful when
|
implementation of the framework itself, but may be also be useful when
|
||||||
implementing extensions.
|
implementing extensions.
|
||||||
|
|
||||||
|
|
||||||
Adding a Workload
|
Adding a Workload
|
||||||
@@ -329,7 +380,7 @@ The Workload class defines the following interface::
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def initialize(self, context):
|
def initialize(self, context):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -359,7 +410,7 @@ The interface should be implemented as follows
|
|||||||
:name: This identifies the workload (e.g. it used to specify it in the
|
:name: This identifies the workload (e.g. it used to specify it in the
|
||||||
agenda_.
|
agenda_.
|
||||||
:init_resources: This method may be optionally override to implement dynamic
|
:init_resources: This method may be optionally override to implement dynamic
|
||||||
resource discovery for the workload. This method executes
|
resource discovery for the workload. This method executes
|
||||||
early on, before the device has been initialized, so it
|
early on, before the device has been initialized, so it
|
||||||
should only be used to initialize resources that do not
|
should only be used to initialize resources that do not
|
||||||
depend on the device to resolve. This method is executed
|
depend on the device to resolve. This method is executed
|
||||||
@@ -368,12 +419,12 @@ The interface should be implemented as follows
|
|||||||
makes about the environment (e.g. that required files are
|
makes about the environment (e.g. that required files are
|
||||||
present, environment variables are set, etc) and should raise
|
present, environment variables are set, etc) and should raise
|
||||||
a :class:`wlauto.exceptions.WorkloadError` if that is not the
|
a :class:`wlauto.exceptions.WorkloadError` if that is not the
|
||||||
case. The base class implementation only makes sure sure that
|
case. The base class implementation only makes sure sure that
|
||||||
the name attribute has been set.
|
the name attribute has been set.
|
||||||
:initialize: This method will be executed exactly once per run (no matter
|
:initialize: This method will be executed exactly once per run (no matter
|
||||||
how many instances of the workload there are). It will run
|
how many instances of the workload there are). It will run
|
||||||
after the device has been initialized, so it may be used to
|
after the device has been initialized, so it may be used to
|
||||||
perform device-dependent initialization that does not need to
|
perform device-dependent initialization that does not need to
|
||||||
be repeated on each iteration (e.g. as installing executables
|
be repeated on each iteration (e.g. as installing executables
|
||||||
required by the workload on the device).
|
required by the workload on the device).
|
||||||
:setup: Everything that needs to be in place for workload execution should
|
:setup: Everything that needs to be in place for workload execution should
|
||||||
@@ -536,17 +587,17 @@ device name(case sensitive) then followed by a dot '.' then the stage name
|
|||||||
then '.revent'. All your custom revent files should reside at
|
then '.revent'. All your custom revent files should reside at
|
||||||
'~/.workload_automation/dependencies/WORKLOAD NAME/'. These are the current
|
'~/.workload_automation/dependencies/WORKLOAD NAME/'. These are the current
|
||||||
supported stages:
|
supported stages:
|
||||||
|
|
||||||
:setup: This stage is where the game is loaded. It is a good place to
|
:setup: This stage is where the game is loaded. It is a good place to
|
||||||
record revent here to modify the game settings and get it ready
|
record revent here to modify the game settings and get it ready
|
||||||
to start.
|
to start.
|
||||||
:run: This stage is where the game actually starts. This will allow for
|
:run: This stage is where the game actually starts. This will allow for
|
||||||
more accurate results if the revent file for this stage only
|
more accurate results if the revent file for this stage only
|
||||||
records the game being played.
|
records the game being played.
|
||||||
|
|
||||||
For instance, to add a custom revent files for a device named mydevice and
|
For instance, to add a custom revent files for a device named mydevice and
|
||||||
a workload name mygame, you create a new directory called mygame in
|
a workload name mygame, you create a new directory called mygame in
|
||||||
'~/.workload_automation/dependencies/'. Then you add the revent files for
|
'~/.workload_automation/dependencies/'. Then you add the revent files for
|
||||||
the stages you want in ~/.workload_automation/dependencies/mygame/::
|
the stages you want in ~/.workload_automation/dependencies/mygame/::
|
||||||
|
|
||||||
mydevice.setup.revent
|
mydevice.setup.revent
|
||||||
@@ -555,7 +606,7 @@ the stages you want in ~/.workload_automation/dependencies/mygame/::
|
|||||||
Any revent file in the dependencies will always overwrite the revent file in the
|
Any revent file in the dependencies will always overwrite the revent file in the
|
||||||
workload directory. So it is possible for example to just provide one revent for
|
workload directory. So it is possible for example to just provide one revent for
|
||||||
setup in the dependencies and use the run.revent that is in the workload directory.
|
setup in the dependencies and use the run.revent that is in the workload directory.
|
||||||
|
|
||||||
Adding an Instrument
|
Adding an Instrument
|
||||||
====================
|
====================
|
||||||
|
|
||||||
@@ -751,19 +802,19 @@ table::
|
|||||||
with open(outfile, 'w') as wfh:
|
with open(outfile, 'w') as wfh:
|
||||||
write_table(rows, wfh)
|
write_table(rows, wfh)
|
||||||
|
|
||||||
|
|
||||||
Adding a Resource Getter
|
Adding a Resource Getter
|
||||||
========================
|
========================
|
||||||
|
|
||||||
A resource getter is a new extension type added in version 2.1.3. A resource
|
A resource getter is a new extension type added in version 2.1.3. A resource
|
||||||
getter implement a method of acquiring resources of a particular type (such as
|
getter implement a method of acquiring resources of a particular type (such as
|
||||||
APK files or additional workload assets). Resource getters are invoked in
|
APK files or additional workload assets). Resource getters are invoked in
|
||||||
priority order until one returns the desired resource.
|
priority order until one returns the desired resource.
|
||||||
|
|
||||||
If you want WA to look for resources somewhere it doesn't by default (e.g. you
|
If you want WA to look for resources somewhere it doesn't by default (e.g. you
|
||||||
have a repository of APK files), you can implement a getter for the resource and
|
have a repository of APK files), you can implement a getter for the resource and
|
||||||
register it with a higher priority than the standard WA getters, so that it gets
|
register it with a higher priority than the standard WA getters, so that it gets
|
||||||
invoked first.
|
invoked first.
|
||||||
|
|
||||||
Instances of a resource getter should implement the following interface::
|
Instances of a resource getter should implement the following interface::
|
||||||
|
|
||||||
@@ -775,7 +826,7 @@ Instances of a resource getter should implement the following interface::
|
|||||||
|
|
||||||
def get(self, resource, **kwargs):
|
def get(self, resource, **kwargs):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
The getter should define a name (as with all extensions), a resource
|
The getter should define a name (as with all extensions), a resource
|
||||||
type, which should be a string, e.g. ``'jar'``, and a priority (see `Getter
|
type, which should be a string, e.g. ``'jar'``, and a priority (see `Getter
|
||||||
Prioritization`_ below). In addition, ``get`` method should be implemented. The
|
Prioritization`_ below). In addition, ``get`` method should be implemented. The
|
||||||
@@ -847,7 +898,7 @@ looks for the file under
|
|||||||
elif not found_files:
|
elif not found_files:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
raise ResourceError('More than one .apk found in {} for {}.'.format(resource_dir,
|
raise ResourceError('More than one .apk found in {} for {}.'.format(resource_dir,
|
||||||
resource.owner.name))
|
resource.owner.name))
|
||||||
|
|
||||||
.. _adding_a_device:
|
.. _adding_a_device:
|
||||||
@@ -947,7 +998,7 @@ top-level package directory is created by default, and it is OK to have
|
|||||||
everything in there.
|
everything in there.
|
||||||
|
|
||||||
.. note:: When discovering extensions thorugh this mechanism, WA traveries the
|
.. note:: When discovering extensions thorugh this mechanism, WA traveries the
|
||||||
Python module/submodule tree, not the directory strucuter, therefore,
|
Python module/submodule tree, not the directory strucuter, therefore,
|
||||||
if you are going to create subdirectories under the top level dictory
|
if you are going to create subdirectories under the top level dictory
|
||||||
created for you, it is important that your make sure they are valid
|
created for you, it is important that your make sure they are valid
|
||||||
Python packages; i.e. each subdirectory must contain a __init__.py
|
Python packages; i.e. each subdirectory must contain a __init__.py
|
||||||
@@ -958,7 +1009,7 @@ At this stage, you may want to edit ``params`` structure near the bottom of
|
|||||||
the ``setup.py`` to add correct author, license and contact information (see
|
the ``setup.py`` to add correct author, license and contact information (see
|
||||||
"Writing the Setup Script" section in standard Python documentation for
|
"Writing the Setup Script" section in standard Python documentation for
|
||||||
details). You may also want to add a README and/or a COPYING file at the same
|
details). You may also want to add a README and/or a COPYING file at the same
|
||||||
level as the setup.py. Once you have the contents of your package sorted,
|
level as the setup.py. Once you have the contents of your package sorted,
|
||||||
you can generate the package by running ::
|
you can generate the package by running ::
|
||||||
|
|
||||||
cd my_wa_exts
|
cd my_wa_exts
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
#
|
#
|
||||||
[MASTER]
|
[MASTER]
|
||||||
|
|
||||||
profile=no
|
#profile=no
|
||||||
|
|
||||||
ignore=external
|
ignore=external
|
||||||
|
|
||||||
|
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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
223
wlauto/commands/record.py
Normal file
223
wlauto/commands/record.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# 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 os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
|
from wlauto import ExtensionLoader, Command, settings
|
||||||
|
from wlauto.common.resources import Executable
|
||||||
|
from wlauto.core.resource import NO_ONE
|
||||||
|
from wlauto.core.resolver import ResourceResolver
|
||||||
|
from wlauto.core.configuration import RunConfiguration
|
||||||
|
from wlauto.core.agenda import Agenda
|
||||||
|
from wlauto.utils.revent import ReventRecording, GAMEPAD_MODE
|
||||||
|
|
||||||
|
|
||||||
|
class ReventCommand(Command):
|
||||||
|
|
||||||
|
# Validate command options
|
||||||
|
def validate_args(self, args):
|
||||||
|
if args.clear and not args.package:
|
||||||
|
print "Package must be specified if you want to clear cache\n"
|
||||||
|
self.parser.print_help()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# pylint: disable=W0201
|
||||||
|
def execute(self, args):
|
||||||
|
self.validate_args(args)
|
||||||
|
self.logger.info("Connecting to device...")
|
||||||
|
|
||||||
|
ext_loader = ExtensionLoader(packages=settings.extension_packages,
|
||||||
|
paths=settings.extension_paths)
|
||||||
|
|
||||||
|
# Setup config
|
||||||
|
self.config = RunConfiguration(ext_loader)
|
||||||
|
for filepath in settings.get_config_paths():
|
||||||
|
self.config.load_config(filepath)
|
||||||
|
self.config.set_agenda(Agenda())
|
||||||
|
self.config.finalize()
|
||||||
|
|
||||||
|
context = LightContext(self.config)
|
||||||
|
|
||||||
|
# Setup device
|
||||||
|
self.device = ext_loader.get_device(settings.device, **settings.device_config)
|
||||||
|
self.device.validate()
|
||||||
|
self.device.dynamic_modules = []
|
||||||
|
self.device.connect()
|
||||||
|
self.device.initialize(context)
|
||||||
|
|
||||||
|
# Only install vsync service on android
|
||||||
|
if self.device.platform is 'android':
|
||||||
|
self.logger.debug("Installing HelloJni for vsync service")
|
||||||
|
host_HelloJni_apk = context.resolver.get(Executable(NO_ONE, self.device.abi, 'HelloJni.apk'))
|
||||||
|
self.target_HelloJni = self.device.install_if_needed(host_HelloJni_apk)
|
||||||
|
result = self.device.execute('dumpsys activity services | grep "ChoreoService"', check_exit_code=False)
|
||||||
|
if not result or 'com.example.hellojni/.ChoreoService' not in result:
|
||||||
|
self.device.execute('am startservice com.example.hellojni/.ChoreoService')
|
||||||
|
|
||||||
|
# Install revent
|
||||||
|
host_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent'))
|
||||||
|
self.target_binary = self.device.install_executable(host_binary)
|
||||||
|
|
||||||
|
self.run(args)
|
||||||
|
|
||||||
|
def run(self, args):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RecordCommand(ReventCommand):
|
||||||
|
|
||||||
|
name = 'record'
|
||||||
|
description = '''Performs a revent recording
|
||||||
|
|
||||||
|
This command helps 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.
|
||||||
|
|
||||||
|
|
||||||
|
**gamepad recording**
|
||||||
|
|
||||||
|
revent supports an alternative recording mode, where it will record events
|
||||||
|
from a single gamepad device. In this mode, revent will store the
|
||||||
|
description of this device as a part of the recording. When replaying such
|
||||||
|
a recording, revent will first create a virtual gamepad using the
|
||||||
|
description, and will replay the events into it, so a physical controller
|
||||||
|
does not need to be connected on replay. Unlike standard revent recordings,
|
||||||
|
recordings generated in this mode should be (to an extent) portable across
|
||||||
|
different devices.
|
||||||
|
|
||||||
|
note:
|
||||||
|
|
||||||
|
- The device on which a recording is being made in gamepad mode, must have
|
||||||
|
exactly one gamepad connected to it.
|
||||||
|
- The device on which a gamepad recording is being replayed must have
|
||||||
|
/dev/uinput enabled in the kernel (this interface is necessary to create
|
||||||
|
virtual gamepad).
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
def initialize(self, context):
|
||||||
|
self.context = context
|
||||||
|
self.parser.add_argument('-d', '--device', help='The name of the device')
|
||||||
|
self.parser.add_argument('-s', '--suffix', help='The suffix of the revent file, e.g. ``setup``')
|
||||||
|
self.parser.add_argument('-o', '--output', help='Directory to save the recording in')
|
||||||
|
self.parser.add_argument('-p', '--package', help='Package to launch before recording')
|
||||||
|
self.parser.add_argument('-g', '--gamepad', help='Record from a gamepad rather than all devices.',
|
||||||
|
action="store_true")
|
||||||
|
self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it',
|
||||||
|
action="store_true")
|
||||||
|
self.parser.add_argument('-S', '--capture-screen', help='Record a screen capture after recording',
|
||||||
|
action="store_true")
|
||||||
|
|
||||||
|
def run(self, args):
|
||||||
|
if args.device:
|
||||||
|
device_name = args.device
|
||||||
|
else:
|
||||||
|
device_name = self.device.get_device_model()
|
||||||
|
|
||||||
|
if args.suffix:
|
||||||
|
args.suffix += "."
|
||||||
|
|
||||||
|
revent_file = self.device.path.join(self.device.working_directory,
|
||||||
|
'{}.{}revent'.format(device_name, args.suffix or ""))
|
||||||
|
|
||||||
|
if args.clear:
|
||||||
|
self.device.execute("pm clear {}".format(args.package))
|
||||||
|
|
||||||
|
if args.package:
|
||||||
|
self.logger.info("Starting {}".format(args.package))
|
||||||
|
self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.format(args.package))
|
||||||
|
|
||||||
|
self.logger.info("Press Enter when you are ready to record...")
|
||||||
|
raw_input("")
|
||||||
|
gamepad_flag = '-g ' if args.gamepad else ''
|
||||||
|
vsync_flag = '-V ' if self.device.platform is 'android' else ''
|
||||||
|
command = "{} record {}{}-s {}".format(self.target_binary, gamepad_flag, vsync_flag, revent_file)
|
||||||
|
self.device.kick_off(command)
|
||||||
|
|
||||||
|
self.logger.info("Press Enter when you have finished recording...")
|
||||||
|
raw_input("")
|
||||||
|
if args.capture_screen:
|
||||||
|
self.logger.info("Recording screen capture")
|
||||||
|
self.device.capture_screen(args.output or os.getcwdu())
|
||||||
|
self.device.killall("revent", signal.SIGINT)
|
||||||
|
self.logger.info("Waiting for revent to finish")
|
||||||
|
while self.device.get_pids_of("revent"):
|
||||||
|
pass
|
||||||
|
self.logger.info("Pulling files from device")
|
||||||
|
self.device.pull_file(revent_file, args.output or os.getcwdu())
|
||||||
|
|
||||||
|
|
||||||
|
class ReplayCommand(ReventCommand):
|
||||||
|
|
||||||
|
name = 'replay'
|
||||||
|
description = '''Replay a revent recording
|
||||||
|
|
||||||
|
Revent allows you to record raw inputs such as screen swipes or button presses.
|
||||||
|
See ``wa show record`` to see how to make an revent recording.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def initialize(self, context):
|
||||||
|
self.context = context
|
||||||
|
self.parser.add_argument('revent', help='The name of the file to replay')
|
||||||
|
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")
|
||||||
|
|
||||||
|
# pylint: disable=W0201
|
||||||
|
def run(self, args):
|
||||||
|
self.logger.info("Pushing file to device")
|
||||||
|
self.device.push_file(args.revent, self.device.working_directory)
|
||||||
|
revent_file = self.device.path.join(self.device.working_directory, os.path.split(args.revent)[1])
|
||||||
|
|
||||||
|
if args.clear:
|
||||||
|
self.device.execute("pm clear {}".format(args.package))
|
||||||
|
|
||||||
|
if args.package:
|
||||||
|
self.logger.info("Starting {}".format(args.package))
|
||||||
|
self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.format(args.package))
|
||||||
|
|
||||||
|
self.logger.info("Replaying recording")
|
||||||
|
vsync_flag = '-V ' if self.device.platform is 'android' else ''
|
||||||
|
command = "{} replay {}{}".format(self.target_binary, vsync_flag, revent_file)
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
# Used to satisfy the API
|
||||||
|
class LightContext(object):
|
||||||
|
def __init__(self, config):
|
||||||
|
self.resolver = ResourceResolver(config)
|
||||||
|
self.resolver.load()
|
@@ -20,6 +20,7 @@ import shutil
|
|||||||
|
|
||||||
import wlauto
|
import wlauto
|
||||||
from wlauto import Command, settings
|
from wlauto import Command, settings
|
||||||
|
from wlauto.exceptions import ConfigError
|
||||||
from wlauto.core.agenda import Agenda
|
from wlauto.core.agenda import Agenda
|
||||||
from wlauto.core.execution import Executor
|
from wlauto.core.execution import Executor
|
||||||
from wlauto.utils.log import add_log_file
|
from wlauto.utils.log import add_log_file
|
||||||
@@ -76,6 +77,11 @@ class RunCommand(Command):
|
|||||||
agenda = Agenda(args.agenda)
|
agenda = Agenda(args.agenda)
|
||||||
settings.agenda = args.agenda
|
settings.agenda = args.agenda
|
||||||
shutil.copy(args.agenda, settings.meta_directory)
|
shutil.copy(args.agenda, settings.meta_directory)
|
||||||
|
|
||||||
|
if len(agenda.workloads) == 0:
|
||||||
|
raise ConfigError("No workloads specified")
|
||||||
|
elif '.' in args.agenda or os.sep in args.agenda:
|
||||||
|
raise ConfigError('Agenda "{}" does not exist.'.format(args.agenda))
|
||||||
else:
|
else:
|
||||||
self.logger.debug('{} is not a file; assuming workload name.'.format(args.agenda))
|
self.logger.debug('{} is not a file; assuming workload name.'.format(args.agenda))
|
||||||
agenda = Agenda()
|
agenda = Agenda()
|
||||||
|
@@ -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))
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ class ${class_name}(AndroidBenchmark):
|
|||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
# Workload parameters go here e.g.
|
# Workload parameters go here e.g.
|
||||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||||
description='This is an example parameter')
|
description='This is an example parameter')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ class ${class_name}(AndroidUiAutoBenchmark):
|
|||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
# Workload parameters go here e.g.
|
# Workload parameters go here e.g.
|
||||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||||
description='This is an example parameter')
|
description='This is an example parameter')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ class ${class_name}(Workload):
|
|||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
# Workload parameters go here e.g.
|
# Workload parameters go here e.g.
|
||||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||||
description='This is an example parameter')
|
description='This is an example parameter')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ class ${class_name}(UiAutomatorWorkload):
|
|||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
# Workload parameters go here e.g.
|
# Workload parameters go here e.g.
|
||||||
Parameter('Example parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
Parameter('example_parameter', kind=int, allowed_values=[1,2,3], default=1, override=True, mandatory=False,
|
||||||
description='This is an example parameter')
|
description='This is an example parameter')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
BIN
wlauto/common/android/BaseUiAutomation$1.class
Normal file
BIN
wlauto/common/android/BaseUiAutomation$1.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/BaseUiAutomation$ActionLogger.class
Normal file
BIN
wlauto/common/android/BaseUiAutomation$ActionLogger.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/BaseUiAutomation$Direction.class
Normal file
BIN
wlauto/common/android/BaseUiAutomation$Direction.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/BaseUiAutomation$FindByCriteria.class
Normal file
BIN
wlauto/common/android/BaseUiAutomation$FindByCriteria.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/BaseUiAutomation$PinchType.class
Normal file
BIN
wlauto/common/android/BaseUiAutomation$PinchType.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/BaseUiAutomation$ScreenOrientation.class
Normal file
BIN
wlauto/common/android/BaseUiAutomation$ScreenOrientation.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$1.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$1.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$ActionLogger.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$ActionLogger.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$Direction.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$Direction.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$GestureTestParams.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$GestureTestParams.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$GestureType.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$GestureType.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$PinchType.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$PinchType.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$SurfaceLogger.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$SurfaceLogger.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$Timer.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$Timer.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation$UxPerfLogger.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation$UxPerfLogger.class
Normal file
Binary file not shown.
BIN
wlauto/common/android/UxPerfUiAutomation.class
Normal file
BIN
wlauto/common/android/UxPerfUiAutomation.class
Normal file
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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -21,18 +21,22 @@ import time
|
|||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
|
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
|
||||||
|
from wlauto.common.resources import Executable
|
||||||
|
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
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn)=([0-9]+|true|false)', re.I)
|
SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|Display Power: state)=([0-9]+|true|false|ON|OFF)', re.I)
|
||||||
SCREEN_SIZE_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)\s+(?P<width>\d+)x(?P<height>\d+)')
|
SCREEN_SIZE_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)\s+(?P<width>\d+)x(?P<height>\d+)')
|
||||||
|
|
||||||
|
|
||||||
@@ -49,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='/system/bin',
|
|
||||||
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).'),
|
||||||
@@ -72,9 +75,10 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
Specified whether the device should make sure that the screen is on
|
Specified whether the device should make sure that the screen is on
|
||||||
during initialization.
|
during initialization.
|
||||||
"""),
|
"""),
|
||||||
Parameter('swipe_to_unlock', kind=boolean, default=False,
|
Parameter('swipe_to_unlock', kind=str, default=None,
|
||||||
|
allowed_values=[None, "horizontal", "vertical"],
|
||||||
description="""
|
description="""
|
||||||
If set to ``True``, a horisonal swipe will be performed 2/3 down the screen.
|
If set a swipe of the specified direction will be performed.
|
||||||
This should unlock the screen.
|
This should unlock the screen.
|
||||||
"""),
|
"""),
|
||||||
]
|
]
|
||||||
@@ -104,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)
|
||||||
@@ -164,7 +183,7 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
raise DeviceError('Could not boot {} ({}).'.format(self.name, self.adb_name))
|
raise DeviceError('Could not boot {} ({}).'.format(self.name, self.adb_name))
|
||||||
|
|
||||||
while iteration_number < max_iterations:
|
while iteration_number < max_iterations:
|
||||||
available = (1 == int('0' + adb_shell(self.adb_name, 'getprop sys.boot_completed', timeout=self.default_timeout)))
|
available = (int('0' + (adb_shell(self.adb_name, 'getprop sys.boot_completed', timeout=self.default_timeout))) == 1)
|
||||||
if available:
|
if available:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -192,9 +211,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
self._is_ready = True
|
self._is_ready = True
|
||||||
|
|
||||||
def initialize(self, context):
|
def initialize(self, context):
|
||||||
self.execute('mkdir -p {}'.format(self.working_directory))
|
self.sqlite = self.deploy_sqlite3(context) # pylint: disable=attribute-defined-outside-init
|
||||||
if self.is_rooted:
|
if self.is_rooted:
|
||||||
self.busybox = self.deploy_busybox(context)
|
|
||||||
self.disable_screen_lock()
|
self.disable_screen_lock()
|
||||||
self.disable_selinux()
|
self.disable_selinux()
|
||||||
if self.enable_screen_check:
|
if self.enable_screen_check:
|
||||||
@@ -236,7 +254,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
.. note:: This will get reset on userdata erasure.
|
.. note:: This will get reset on userdata erasure.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.execute('settings get secure android_id').strip()
|
output = self.execute('content query --uri content://settings/secure --projection value --where "name=\'android_id\'"').strip()
|
||||||
|
return output.split('value=')[-1]
|
||||||
|
|
||||||
def get_sdk_version(self):
|
def get_sdk_version(self):
|
||||||
try:
|
try:
|
||||||
@@ -258,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.
|
||||||
@@ -279,11 +316,24 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
"""
|
"""
|
||||||
return package_name in self.list_packages()
|
return package_name in self.list_packages()
|
||||||
|
|
||||||
def executable_is_installed(self, executable_name):
|
def executable_is_installed(self, executable_name): # pylint: disable=unused-argument,no-self-use
|
||||||
return executable_name in self.listdir(self.binaries_directory)
|
raise AttributeError("""Instead of using is_installed, please use
|
||||||
|
``get_binary_path`` or ``install_if_needed`` instead. You should
|
||||||
|
use the path returned by these functions to then invoke the binary
|
||||||
|
|
||||||
|
please see: https://pythonhosted.org/wlauto/writing_extensions.html""")
|
||||||
|
|
||||||
def is_installed(self, name):
|
def is_installed(self, name):
|
||||||
return self.executable_is_installed(name) or self.package_is_installed(name)
|
if self.package_is_installed(name):
|
||||||
|
return True
|
||||||
|
elif "." in name: # assumes android packages have a . in their name and binaries documentation
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise AttributeError("""Instead of using is_installed, please use
|
||||||
|
``get_binary_path`` or ``install_if_needed`` instead. You should
|
||||||
|
use the path returned by these functions to then invoke the binary
|
||||||
|
|
||||||
|
please see: https://pythonhosted.org/wlauto/writing_extensions.html""")
|
||||||
|
|
||||||
def listdir(self, path, as_root=False, **kwargs):
|
def listdir(self, path, as_root=False, **kwargs):
|
||||||
contents = self.execute('ls {}'.format(path), as_root=as_root)
|
contents = self.execute('ls {}'.format(path), as_root=as_root)
|
||||||
@@ -325,35 +375,40 @@ 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()
|
||||||
output = adb_shell(self.adb_name, 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath),
|
output = adb_shell(self.adb_name, 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath),
|
||||||
timeout=self.default_timeout)
|
timeout=self.default_timeout)
|
||||||
if int(output):
|
return bool(int(output))
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
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=default_timeout, 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))
|
||||||
|
|
||||||
def install_executable(self, filepath, with_name=None):
|
def install_executable(self, filepath, with_name=None):
|
||||||
"""
|
"""
|
||||||
Installs a binary executable on device. Requires root access. Returns
|
Installs a binary executable on device. Returns
|
||||||
the path to the installed binary, or ``None`` if the installation has failed.
|
the path to the installed binary, or ``None`` if the installation has failed.
|
||||||
Optionally, ``with_name`` parameter may be used to specify a different name under
|
Optionally, ``with_name`` parameter may be used to specify a different name under
|
||||||
which the executable will be installed.
|
which the executable will be installed.
|
||||||
@@ -377,12 +432,13 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
|
|
||||||
def uninstall_executable(self, executable_name):
|
def uninstall_executable(self, executable_name):
|
||||||
"""
|
"""
|
||||||
Requires root access.
|
|
||||||
|
|
||||||
Added in version 2.1.3.
|
Added in version 2.1.3.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
on_device_executable = self.path.join(self.binaries_directory, executable_name)
|
on_device_executable = self.get_binary_path(executable_name, search_system_binaries=False)
|
||||||
|
if not on_device_executable:
|
||||||
|
raise DeviceError("Could not uninstall {}, binary not found".format(on_device_executable))
|
||||||
self._ensure_binaries_directory_is_writable()
|
self._ensure_binaries_directory_is_writable()
|
||||||
self.delete_file(on_device_executable, as_root=self.is_rooted)
|
self.delete_file(on_device_executable, as_root=self.is_rooted)
|
||||||
|
|
||||||
@@ -406,7 +462,7 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
|
|
||||||
Added in version 2.1.3
|
Added in version 2.1.3
|
||||||
|
|
||||||
.. note:: The device must be rooted to be able to use busybox.
|
.. note:: The device must be rooted to be able to use some busybox features.
|
||||||
|
|
||||||
:param as_root: If ``True``, will attempt to execute command in privileged mode. The device
|
:param as_root: If ``True``, will attempt to execute command in privileged mode. The device
|
||||||
must be rooted, otherwise an error will be raised. Defaults to ``False``.
|
must be rooted, otherwise an error will be raised. Defaults to ``False``.
|
||||||
@@ -425,31 +481,26 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
if as_root and not self.is_rooted:
|
if as_root and not self.is_rooted:
|
||||||
raise DeviceError('Attempting to execute "{}" as root on unrooted device.'.format(command))
|
raise DeviceError('Attempting to execute "{}" as root on unrooted device.'.format(command))
|
||||||
if busybox:
|
if busybox:
|
||||||
if not self.is_rooted:
|
|
||||||
DeviceError('Attempting to execute "{}" with busybox. '.format(command) +
|
|
||||||
'Busybox can only be deployed to rooted devices.')
|
|
||||||
command = ' '.join([self.busybox, command])
|
command = ' '.join([self.busybox, command])
|
||||||
if background:
|
if background:
|
||||||
return adb_background_shell(self.adb_name, command, as_root=as_root)
|
return adb_background_shell(self.adb_name, command, as_root=as_root)
|
||||||
else:
|
else:
|
||||||
return adb_shell(self.adb_name, command, timeout, check_exit_code, as_root)
|
return adb_shell(self.adb_name, command, timeout, check_exit_code, as_root)
|
||||||
|
|
||||||
def kick_off(self, command):
|
def kick_off(self, command, as_root=None):
|
||||||
"""
|
"""
|
||||||
Like execute but closes adb session and returns immediately, leaving the command running on the
|
Like execute but closes adb session and returns immediately, leaving the command running on the
|
||||||
device (this is different from execute(background=True) which keeps adb connection open and returns
|
device (this is different from execute(background=True) which keeps adb connection open and returns
|
||||||
a subprocess object).
|
a subprocess object).
|
||||||
|
|
||||||
.. note:: This relies on busybox's nohup applet and so won't work on unrooted devices.
|
|
||||||
|
|
||||||
Added in version 2.1.4
|
Added in version 2.1.4
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.is_rooted:
|
if as_root is None:
|
||||||
raise DeviceError('kick_off uses busybox\'s nohup applet and so can only be run a rooted device.')
|
as_root = self.is_rooted
|
||||||
try:
|
try:
|
||||||
command = 'cd {} && busybox nohup {}'.format(self.working_directory, command)
|
command = 'cd {} && {} nohup {}'.format(self.working_directory, self.busybox, command)
|
||||||
output = self.execute(command, timeout=1, as_root=True)
|
output = self.execute(command, timeout=1, as_root=as_root)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -457,9 +508,10 @@ 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 {}'.format(process_name[-15:]), check_exit_code=False).strip()
|
result = (self.execute('ps | {} grep {}'.format(self.busybox, process_name),
|
||||||
|
check_exit_code=False) or '').strip()
|
||||||
if result and 'not found' not in result:
|
if result and 'not found' not in result:
|
||||||
return [int(x.split()[1]) for x in result.split('\n')[1:]]
|
return [int(x.split()[1]) for x in result.split('\n')]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -496,17 +548,17 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
def _get_android_properties(self, context):
|
def _get_android_properties(self, context):
|
||||||
props = {}
|
props = {}
|
||||||
props['android_id'] = self.get_android_id()
|
props['android_id'] = self.get_android_id()
|
||||||
buildprop_file = os.path.join(context.host_working_directory, 'build.prop')
|
self._update_build_properties(props)
|
||||||
if not os.path.isfile(buildprop_file):
|
|
||||||
self.pull_file('/system/build.prop', context.host_working_directory)
|
|
||||||
self._update_build_properties(buildprop_file, props)
|
|
||||||
context.add_run_artifact('build_properties', buildprop_file, 'export')
|
|
||||||
|
|
||||||
dumpsys_target_file = self.device.path.join(self.device.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')
|
||||||
|
with open(prop_file, 'w') as wfh:
|
||||||
|
json.dump(props, wfh)
|
||||||
|
context.add_run_artifact('android_properties', prop_file, 'export')
|
||||||
return props
|
return props
|
||||||
|
|
||||||
def getprop(self, prop=None):
|
def getprop(self, prop=None):
|
||||||
@@ -520,6 +572,11 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
return props[prop]
|
return props[prop]
|
||||||
return props
|
return props
|
||||||
|
|
||||||
|
def deploy_sqlite3(self, context):
|
||||||
|
host_file = context.resolver.get(Executable(NO_ONE, self.abi, 'sqlite3'))
|
||||||
|
target_file = self.install_if_needed(host_file)
|
||||||
|
return target_file
|
||||||
|
|
||||||
# Android-specific methods. These either rely on specifics of adb or other
|
# Android-specific methods. These either rely on specifics of adb or other
|
||||||
# Android-only concepts in their interface and/or implementation.
|
# Android-only concepts in their interface and/or implementation.
|
||||||
|
|
||||||
@@ -572,13 +629,20 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
else:
|
else:
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
|
|
||||||
def swipe_to_unlock(self):
|
def perform_unlock_swipe(self):
|
||||||
width, height = self.get_screen_size()
|
width, height = self.get_screen_size()
|
||||||
swipe_heigh = height * 2 // 3
|
|
||||||
start = 100
|
|
||||||
stop = width - start
|
|
||||||
command = 'input swipe {} {} {} {}'
|
command = 'input swipe {} {} {} {}'
|
||||||
self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
|
if self.swipe_to_unlock == "horizontal":
|
||||||
|
swipe_heigh = height * 2 // 3
|
||||||
|
start = 100
|
||||||
|
stop = width - start
|
||||||
|
self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
|
||||||
|
if self.swipe_to_unlock == "vertical":
|
||||||
|
swipe_middle = height / 2
|
||||||
|
swipe_heigh = height * 2 // 3
|
||||||
|
self.execute(command.format(swipe_middle, swipe_heigh, swipe_middle, 0))
|
||||||
|
else: # Should never reach here
|
||||||
|
raise DeviceError("Invalid swipe direction: {}".format(self.swipe_to_unlock))
|
||||||
|
|
||||||
def capture_screen(self, filepath):
|
def capture_screen(self, filepath):
|
||||||
"""Caputers the current device screen into the specified file in a PNG format."""
|
"""Caputers the current device screen into the specified file in a PNG format."""
|
||||||
@@ -587,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')
|
||||||
@@ -599,6 +674,8 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
def ensure_screen_is_on(self):
|
def ensure_screen_is_on(self):
|
||||||
if not self.is_screen_on():
|
if not self.is_screen_on():
|
||||||
self.execute('input keyevent 26')
|
self.execute('input keyevent 26')
|
||||||
|
if self.swipe_to_unlock:
|
||||||
|
self.perform_unlock_swipe()
|
||||||
|
|
||||||
def disable_screen_lock(self):
|
def disable_screen_lock(self):
|
||||||
"""
|
"""
|
||||||
@@ -610,8 +687,16 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
lockdb = '/data/system/locksettings.db'
|
lockdb = '/data/system/locksettings.db'
|
||||||
sqlcommand = "update locksettings set value=\\'0\\' where name=\\'screenlock.disabled\\';"
|
sqlcommand = "update locksettings set value='0' where name='screenlock.disabled';"
|
||||||
self.execute('sqlite3 {} "{}"'.format(lockdb, sqlcommand), as_root=True)
|
f = tempfile.NamedTemporaryFile()
|
||||||
|
try:
|
||||||
|
f.write('{} {} "{}"'.format(self.sqlite, lockdb, sqlcommand))
|
||||||
|
f.flush()
|
||||||
|
on_device_executable = self.install_executable(f.name,
|
||||||
|
with_name="disable_screen_lock")
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
self.execute(on_device_executable, as_root=True)
|
||||||
|
|
||||||
def disable_selinux(self):
|
def disable_selinux(self):
|
||||||
# This may be invoked from intialize() so we can't use execute() or the
|
# This may be invoked from intialize() so we can't use execute() or the
|
||||||
@@ -625,17 +710,30 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223
|
|||||||
if se_status == 'Enforcing':
|
if se_status == 'Enforcing':
|
||||||
self.execute('setenforce 0', as_root=True)
|
self.execute('setenforce 0', as_root=True)
|
||||||
|
|
||||||
|
def get_device_model(self):
|
||||||
|
try:
|
||||||
|
return self.getprop(prop='ro.product.device')
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def broadcast_media_mounted(self, dirpath):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
# Internal methods: do not use outside of the class.
|
# Internal methods: do not use outside of the class.
|
||||||
|
|
||||||
def _update_build_properties(self, filepath, props):
|
def _update_build_properties(self, props):
|
||||||
try:
|
try:
|
||||||
with open(filepath) as fh:
|
def strip(somestring):
|
||||||
for line in fh:
|
return somestring.strip().replace('[', '').replace(']', '')
|
||||||
line = re.sub(r'#.*', '', line).strip()
|
for line in self.execute("getprop").splitlines():
|
||||||
if not line:
|
key, value = line.split(':', 1)
|
||||||
continue
|
key = strip(key)
|
||||||
key, value = line.split('=', 1)
|
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.')
|
||||||
|
|
||||||
@@ -739,4 +837,3 @@ class BigLittleDevice(AndroidDevice): # pylint: disable=W0223
|
|||||||
parameters = [
|
parameters = [
|
||||||
Parameter('scheduler', default='hmp', override=True),
|
Parameter('scheduler', default='hmp', override=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -34,3 +34,10 @@ class JarFile(FileResource):
|
|||||||
class ApkFile(FileResource):
|
class ApkFile(FileResource):
|
||||||
|
|
||||||
name = 'apk'
|
name = 'apk'
|
||||||
|
|
||||||
|
def __init__(self, owner, platform=None):
|
||||||
|
super(ApkFile, self).__init__(owner)
|
||||||
|
self.platform = platform
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '<{}\'s {} APK>'.format(self.owner, self.platform)
|
||||||
|
520
wlauto/common/android/workload.py
Normal file → Executable file
520
wlauto/common/android/workload.py
Normal file → Executable file
@@ -16,19 +16,30 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
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.core.resource import NO_ONE
|
||||||
from wlauto.common.resources import ExtensionAsset, Executable
|
from wlauto.common.android.resources import ApkFile, ReventFile
|
||||||
from wlauto.exceptions import WorkloadError, ResourceError, ConfigError
|
from wlauto.common.resources import ExtensionAsset, Executable, File
|
||||||
from wlauto.utils.android import ApkInfo
|
from wlauto.exceptions import WorkloadError, ResourceError, DeviceError
|
||||||
|
from wlauto.utils.android import ApkInfo, ANDROID_NORMAL_PERMISSIONS, UNSUPPORTED_PACKAGES
|
||||||
from wlauto.utils.types import boolean
|
from wlauto.utils.types import boolean
|
||||||
|
from wlauto.utils.revent import ReventRecording
|
||||||
|
import wlauto.utils.statedetect as state_detector
|
||||||
import wlauto.common.android.resources
|
import wlauto.common.android.resources
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +77,7 @@ class UiAutomatorWorkload(Workload):
|
|||||||
|
|
||||||
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.device_uiauto_file = None
|
||||||
self.command = None
|
self.command = None
|
||||||
@@ -82,12 +93,13 @@ class UiAutomatorWorkload(Workload):
|
|||||||
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):
|
||||||
|
Workload.setup(self, context)
|
||||||
method_string = '{}.{}#{}'.format(self.uiauto_package, self.uiauto_class, self.uiauto_method)
|
method_string = '{}.{}#{}'.format(self.uiauto_package, self.uiauto_class, self.uiauto_method)
|
||||||
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.iteritems():
|
||||||
params += ' -e {} {}'.format(k, v)
|
params += ' -e {} "{}"'.format(k, v)
|
||||||
self.command = 'uiautomator runtest {}{} -c {}'.format(self.device_uiauto_file, params, method_string)
|
self.command = 'uiautomator runtest {}{} -c {}'.format(self.device_uiauto_file, params, method_string)
|
||||||
self.device.push_file(self.uiauto_file, self.device_uiauto_file)
|
self.device.push_file(self.uiauto_file, self.device_uiauto_file)
|
||||||
self.device.killall('uiautomator')
|
self.device.killall('uiautomator')
|
||||||
@@ -122,10 +134,16 @@ class ApkWorkload(Workload):
|
|||||||
:package: The package name of the app. This is usually a Java-style name of the form
|
:package: The package name of the app. This is usually a Java-style name of the form
|
||||||
``com.companyname.appname``.
|
``com.companyname.appname``.
|
||||||
:activity: This is the initial activity of the app. This will be used to launch the
|
:activity: This is the initial activity of the app. This will be used to launch the
|
||||||
app during the setup.
|
app during the setup. Many applications do not specify a launch activity so
|
||||||
|
this may be left blank if necessary.
|
||||||
:view: The class of the main view pane of the app. This needs to be defined in order
|
: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.
|
||||||
@@ -135,6 +153,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.
|
||||||
|
|
||||||
@@ -142,91 +163,230 @@ 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.
|
||||||
|
'''),
|
||||||
]
|
]
|
||||||
|
|
||||||
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
|
||||||
|
self.exact_abi = kwargs.get('exact_abi')
|
||||||
|
|
||||||
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.start_activity()
|
|
||||||
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:
|
||||||
else:
|
target_version = LooseVersion(target_version)
|
||||||
if not installed_version:
|
self.logger.debug("Found version '{}' on target device".format(target_version))
|
||||||
message = '''{} not found found on the device and check_apk is set to "False"
|
|
||||||
so host version was not checked.'''
|
|
||||||
raise WorkloadError(message.format(self.package))
|
|
||||||
message = 'Version {} installed on device; skipping host APK check.'
|
|
||||||
self.logger.debug(message.format(installed_version))
|
|
||||||
self.reset(context)
|
|
||||||
self.apk_version = installed_version
|
|
||||||
|
|
||||||
def initialize_with_host_apk(self, context, installed_version):
|
# Get host version
|
||||||
host_version = ApkInfo(self.apk_file).version_name
|
self.apk_file = context.resolver.get(ApkFile(self, self.device.abi),
|
||||||
if installed_version != host_version:
|
version=getattr(self, 'version', None),
|
||||||
if installed_version:
|
variant_name=getattr(self, 'variant_name', None),
|
||||||
message = '{} host version: {}, device version: {}; re-installing...'
|
strict=False)
|
||||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
|
||||||
host_version, installed_version))
|
# Get target abi
|
||||||
else:
|
target_abi = self.device.get_installed_package_abi(self.package)
|
||||||
message = '{} host version: {}, not found on device; installing...'
|
if target_abi:
|
||||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
self.logger.debug("Found apk with primary abi '{}' on target device".format(target_abi))
|
||||||
host_version))
|
|
||||||
self.force_install = True # pylint: disable=attribute-defined-outside-init
|
# Get host version, primary abi is first, and then try to find supported.
|
||||||
else:
|
for abi in self.device.supported_abi:
|
||||||
message = '{} version {} found on both device and host.'
|
self.apk_file = context.resolver.get(ApkFile(self, abi),
|
||||||
self.logger.debug(message.format(os.path.basename(self.apk_file),
|
version=getattr(self, 'version', None),
|
||||||
host_version))
|
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 = None
|
||||||
|
if self.apk_file is not None:
|
||||||
|
host_version = ApkInfo(self.apk_file).version_name
|
||||||
|
if host_version:
|
||||||
|
host_version = LooseVersion(host_version)
|
||||||
|
self.logger.debug("Found version '{}' on host".format(host_version))
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
|
||||||
|
# Ensure the apk is setup on the device
|
||||||
if self.force_install:
|
if self.force_install:
|
||||||
if installed_version:
|
self.force_install_apk(context, host_version)
|
||||||
self.device.uninstall(self.package)
|
elif self.check_apk:
|
||||||
self.install_apk(context)
|
self.prefer_host_apk(context, host_version, target_version)
|
||||||
else:
|
else:
|
||||||
self.reset(context)
|
self.prefer_target_apk(context, host_version, target_version)
|
||||||
self.apk_version = host_version
|
|
||||||
|
|
||||||
def start_activity(self):
|
self.reset(context)
|
||||||
output = self.device.execute('am start -W -n {}/{}'.format(self.package, self.activity))
|
self.apk_version = self.device.get_installed_package_version(self.package)
|
||||||
|
context.add_classifiers(apk_version=self.apk_version)
|
||||||
|
|
||||||
|
def launch_application(self):
|
||||||
|
if self.launch_main:
|
||||||
|
self.launch_package() # launch default activity without intent data
|
||||||
|
|
||||||
|
def kill_background(self):
|
||||||
|
self.device.execute('am kill-all') # kill all *background* activities
|
||||||
|
|
||||||
|
def force_install_apk(self, context, host_version):
|
||||||
|
if host_version is None:
|
||||||
|
raise ResourceError("force_install is 'True' but could not find APK on the host")
|
||||||
|
try:
|
||||||
|
self.validate_version(host_version)
|
||||||
|
except ResourceError as e:
|
||||||
|
msg = "force_install is 'True' but the host version is invalid:\n\t{}"
|
||||||
|
raise ResourceError(msg.format(str(e)))
|
||||||
|
self.install_apk(context, replace=True)
|
||||||
|
|
||||||
|
def prefer_host_apk(self, context, host_version, target_version):
|
||||||
|
msg = "check_apk is 'True' "
|
||||||
|
if host_version is None:
|
||||||
|
try:
|
||||||
|
self.validate_version(target_version)
|
||||||
|
except ResourceError as e:
|
||||||
|
msg += "but the APK was not found on the host and the target version is invalid:\n\t{}"
|
||||||
|
raise ResourceError(msg.format(str(e)))
|
||||||
|
else:
|
||||||
|
msg += "but the APK was not found on the host, using target version"
|
||||||
|
self.logger.debug(msg)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.validate_version(host_version)
|
||||||
|
except ResourceError as e1:
|
||||||
|
msg += "but the host APK version is invalid:\n\t{}\n"
|
||||||
|
if target_version is None:
|
||||||
|
msg += "The target does not have the app either"
|
||||||
|
raise ResourceError(msg.format(str(e1)))
|
||||||
|
try:
|
||||||
|
self.validate_version(target_version)
|
||||||
|
except ResourceError as e2:
|
||||||
|
msg += "The target version is also invalid:\n\t{}"
|
||||||
|
raise ResourceError(msg.format(str(e1), str(e2)))
|
||||||
|
else:
|
||||||
|
msg += "using the target version instead"
|
||||||
|
self.logger.debug(msg.format(str(e1)))
|
||||||
|
else: # Host version is valid
|
||||||
|
if target_version is not None and target_version == host_version:
|
||||||
|
msg += " and a matching version is alread on the device, doing nothing"
|
||||||
|
self.logger.debug(msg)
|
||||||
|
return
|
||||||
|
msg += " and the host version is not on the target, installing APK"
|
||||||
|
self.logger.debug(msg)
|
||||||
|
self.install_apk(context, replace=True)
|
||||||
|
|
||||||
|
def prefer_target_apk(self, context, host_version, target_version):
|
||||||
|
msg = "check_apk is 'False' "
|
||||||
|
if target_version is None:
|
||||||
|
try:
|
||||||
|
self.validate_version(host_version)
|
||||||
|
except ResourceError as e:
|
||||||
|
msg += "but the app was not found on the target and the host version is invalid:\n\t{}"
|
||||||
|
raise ResourceError(msg.format(str(e)))
|
||||||
|
else:
|
||||||
|
msg += "and the app was not found on the target, using host version"
|
||||||
|
self.logger.debug(msg)
|
||||||
|
self.install_apk(context)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.validate_version(target_version)
|
||||||
|
except ResourceError as e1:
|
||||||
|
msg += "but the target app version is invalid:\n\t{}\n"
|
||||||
|
if host_version is None:
|
||||||
|
msg += "The host does not have the APK either"
|
||||||
|
raise ResourceError(msg.format(str(e1)))
|
||||||
|
try:
|
||||||
|
self.validate_version(host_version)
|
||||||
|
except ResourceError as e2:
|
||||||
|
msg += "The host version is also invalid:\n\t{}"
|
||||||
|
raise ResourceError(msg.format(str(e1), str(e2)))
|
||||||
|
else:
|
||||||
|
msg += "Using the host APK instead"
|
||||||
|
self.logger.debug(msg.format(str(e1)))
|
||||||
|
self.install_apk(context, replace=True)
|
||||||
|
else:
|
||||||
|
msg += "and a valid version of the app is already on the target, using target app"
|
||||||
|
self.logger.debug(msg)
|
||||||
|
|
||||||
|
def validate_version(self, version):
|
||||||
|
min_apk_version = getattr(self, 'min_apk_version', None)
|
||||||
|
max_apk_version = getattr(self, 'max_apk_version', None)
|
||||||
|
|
||||||
|
if min_apk_version is not None and max_apk_version is not None:
|
||||||
|
if version < LooseVersion(min_apk_version) or \
|
||||||
|
version > LooseVersion(max_apk_version):
|
||||||
|
msg = "version '{}' not supported. " \
|
||||||
|
"Minimum version required: '{}', Maximum version known to work: '{}'"
|
||||||
|
raise ResourceError(msg.format(version, min_apk_version, max_apk_version))
|
||||||
|
|
||||||
|
elif min_apk_version is not None:
|
||||||
|
if version < LooseVersion(min_apk_version):
|
||||||
|
msg = "version '{}' not supported. " \
|
||||||
|
"Minimum version required: '{}'"
|
||||||
|
raise ResourceError(msg.format(version, min_apk_version))
|
||||||
|
|
||||||
|
elif max_apk_version is not None:
|
||||||
|
if version > LooseVersion(max_apk_version):
|
||||||
|
msg = "version '{}' not supported. " \
|
||||||
|
"Maximum version known to work: '{}'"
|
||||||
|
raise ResourceError(msg.format(version, max_apk_version))
|
||||||
|
|
||||||
|
def launch_package(self):
|
||||||
|
if not self.activity:
|
||||||
|
output = self.device.execute('am start -W {}'.format(self.package))
|
||||||
|
else:
|
||||||
|
output = self.device.execute('am start -W -n {}/{}'.format(self.package, self.activity))
|
||||||
if 'Error:' in output:
|
if 'Error:' in output:
|
||||||
self.device.execute('am force-stop {}'.format(self.package)) # this will dismiss any erro dialogs
|
self.device.execute('am force-stop {}'.format(self.package)) # this will dismiss any erro dialogs
|
||||||
raise WorkloadError(output)
|
raise WorkloadError(output)
|
||||||
@@ -236,19 +396,62 @@ class ApkWorkload(Workload):
|
|||||||
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))
|
self.device.execute('pm clear {}'.format(self.package))
|
||||||
|
|
||||||
def install_apk(self, context):
|
# As of android API level 23, apps can request permissions at runtime,
|
||||||
output = self.device.install(self.apk_file, self.install_timeout)
|
# this will grant all of them so requests do not pop up when running the app
|
||||||
|
# This can also be done less "manually" during adb install using the -g flag
|
||||||
|
if self.device.get_sdk_version() >= 23:
|
||||||
|
self._grant_requested_permissions()
|
||||||
|
|
||||||
|
def install_apk(self, context, replace=False):
|
||||||
|
success = False
|
||||||
|
if replace:
|
||||||
|
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?)')
|
||||||
|
self.reset(context)
|
||||||
else:
|
else:
|
||||||
raise WorkloadError(output)
|
raise WorkloadError(output)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(output)
|
self.logger.debug(output)
|
||||||
|
success = True
|
||||||
self.do_post_install(context)
|
self.do_post_install(context)
|
||||||
|
return success
|
||||||
|
|
||||||
|
def _grant_requested_permissions(self):
|
||||||
|
dumpsys_output = self.device.execute(command="dumpsys package {}".format(self.package))
|
||||||
|
permissions = []
|
||||||
|
lines = iter(dumpsys_output.splitlines())
|
||||||
|
for line in lines:
|
||||||
|
if "requested permissions:" in line:
|
||||||
|
break
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if "android.permission." in line:
|
||||||
|
permissions.append(line.split(":")[0].strip())
|
||||||
|
# Matching either of these means the end of requested permissions section
|
||||||
|
elif "install permissions:" in line or "runtime permissions:" in line:
|
||||||
|
break
|
||||||
|
|
||||||
|
for permission in set(permissions):
|
||||||
|
# "Normal" Permisions are automatically granted and cannot be changed
|
||||||
|
permission_name = permission.rsplit('.', 1)[1]
|
||||||
|
if permission_name not in ANDROID_NORMAL_PERMISSIONS:
|
||||||
|
# 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):
|
||||||
@@ -272,35 +475,59 @@ AndroidBenchmark = ApkWorkload # backward compatibility
|
|||||||
|
|
||||||
|
|
||||||
class ReventWorkload(Workload):
|
class ReventWorkload(Workload):
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
default_setup_timeout = 5 * 60 # in seconds
|
|
||||||
default_run_timeout = 10 * 60 # in seconds
|
|
||||||
|
|
||||||
def __init__(self, device, _call_super=True, **kwargs):
|
def __init__(self, device, _call_super=True, **kwargs):
|
||||||
if _call_super:
|
if _call_super:
|
||||||
super(ReventWorkload, self).__init__(device, **kwargs)
|
Workload.__init__(self, device, **kwargs)
|
||||||
devpath = self.device.path
|
devpath = self.device.path
|
||||||
self.on_device_revent_binary = devpath.join(self.device.working_directory, 'revent')
|
self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent')
|
||||||
self.on_device_setup_revent = devpath.join(self.device.working_directory, '{}.setup.revent'.format(self.device.name))
|
self.on_device_HelloJni_apk = devpath.join(self.device.binaries_directory, 'HelloJni.apk')
|
||||||
self.on_device_run_revent = devpath.join(self.device.working_directory, '{}.run.revent'.format(self.device.name))
|
self.setup_timeout = kwargs.get('setup_timeout', None)
|
||||||
self.setup_timeout = kwargs.get('setup_timeout', self.default_setup_timeout)
|
self.run_timeout = kwargs.get('run_timeout', None)
|
||||||
self.run_timeout = kwargs.get('run_timeout', self.default_run_timeout)
|
|
||||||
self.revent_setup_file = None
|
self.revent_setup_file = None
|
||||||
self.revent_run_file = None
|
self.revent_run_file = None
|
||||||
|
self.on_device_setup_revent = None
|
||||||
|
self.on_device_run_revent = None
|
||||||
|
self.statedefs_dir = None
|
||||||
|
|
||||||
def init_resources(self, context):
|
if self.check_states:
|
||||||
self.revent_setup_file = context.resolver.get(wlauto.common.android.resources.ReventFile(self, 'setup'))
|
state_detector.check_match_state_dependencies()
|
||||||
self.revent_run_file = context.resolver.get(wlauto.common.android.resources.ReventFile(self, 'run'))
|
|
||||||
|
|
||||||
def setup(self, context):
|
def setup(self, context):
|
||||||
|
self.device.killall('revent', signal='SIGKILL')
|
||||||
|
self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup'))
|
||||||
|
self.revent_run_file = context.resolver.get(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)
|
self._check_revent_files(context)
|
||||||
self.device.killall('revent')
|
default_setup_timeout = ceil(ReventRecording(self.revent_setup_file).duration) + 30
|
||||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent)
|
default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30
|
||||||
|
self.setup_timeout = self.setup_timeout or default_setup_timeout
|
||||||
|
self.run_timeout = self.run_timeout or default_run_timeout
|
||||||
|
|
||||||
|
Workload.setup(self, context)
|
||||||
|
if self.device.platform is 'android':
|
||||||
|
result = self.device.execute('dumpsys activity services | grep "ChoreoService"',
|
||||||
|
check_exit_code=False)
|
||||||
|
if not result or 'com.example.hellojni/.ChoreoService' not in result:
|
||||||
|
self.logger.debug('Starting VSync Service')
|
||||||
|
self.device.execute('am startservice com.example.hellojni/.ChoreoService')
|
||||||
|
time.sleep(5) # Allow time for service to start
|
||||||
|
vsync_flag = '-V ' if self.device.platform is 'android' else ''
|
||||||
|
command = '{} replay {}{}'.format(self.on_device_revent_binary, vsync_flag, self.on_device_setup_revent)
|
||||||
self.device.execute(command, timeout=self.setup_timeout)
|
self.device.execute(command, timeout=self.setup_timeout)
|
||||||
|
|
||||||
def run(self, context):
|
def run(self, context):
|
||||||
command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent)
|
if self.device.platform is 'android':
|
||||||
|
self.device.execute('am startservice com.example.hellojni/.ChoreoService')
|
||||||
|
time.sleep(5) # Allow time for service to start
|
||||||
self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent)))
|
self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent)))
|
||||||
|
vsync_flag = '-V ' if self.device.platform is 'android' else ''
|
||||||
|
command = '{} replay {}{}'.format(self.on_device_revent_binary, vsync_flag, self.on_device_run_revent)
|
||||||
self.device.execute(command, timeout=self.run_timeout)
|
self.device.execute(command, timeout=self.run_timeout)
|
||||||
self.logger.debug('Replay completed.')
|
self.logger.debug('Replay completed.')
|
||||||
|
|
||||||
@@ -308,6 +535,8 @@ class ReventWorkload(Workload):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def teardown(self, context):
|
def teardown(self, context):
|
||||||
|
self.device.killall('revent')
|
||||||
|
self.device.killall("com.example.hellojni")
|
||||||
self.device.delete_file(self.on_device_setup_revent)
|
self.device.delete_file(self.on_device_setup_revent)
|
||||||
self.device.delete_file(self.on_device_run_revent)
|
self.device.delete_file(self.on_device_run_revent)
|
||||||
|
|
||||||
@@ -318,6 +547,10 @@ class ReventWorkload(Workload):
|
|||||||
message = '{} does not exist. '.format(revent_binary)
|
message = '{} does not exist. '.format(revent_binary)
|
||||||
message += 'Please build revent for your system and place it in that location'
|
message += 'Please build revent for your system and place it in that location'
|
||||||
raise WorkloadError(message)
|
raise WorkloadError(message)
|
||||||
|
if self.device.platform is 'android':
|
||||||
|
HelloJni_APK = context.resolver.get(Executable(NO_ONE, self.device.abi, 'HelloJni.apk'))
|
||||||
|
if not os.path.isfile(HelloJni_APK):
|
||||||
|
message = '{} does not exist. '.format(HelloJni_APK)
|
||||||
if not self.revent_setup_file:
|
if not self.revent_setup_file:
|
||||||
# pylint: disable=too-few-format-args
|
# 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)
|
message = '{0}.setup.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name)
|
||||||
@@ -328,9 +561,30 @@ class ReventWorkload(Workload):
|
|||||||
raise WorkloadError(message)
|
raise WorkloadError(message)
|
||||||
|
|
||||||
self.on_device_revent_binary = self.device.install_executable(revent_binary)
|
self.on_device_revent_binary = self.device.install_executable(revent_binary)
|
||||||
|
if self.device.platform is 'android':
|
||||||
|
self.on_device_HelloJni_apk = self.device.install_if_needed(HelloJni_APK)
|
||||||
self.device.push_file(self.revent_run_file, self.on_device_run_revent)
|
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)
|
self.device.push_file(self.revent_setup_file, self.on_device_setup_revent)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark):
|
||||||
|
|
||||||
@@ -340,6 +594,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)
|
||||||
@@ -356,6 +615,88 @@ 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):
|
||||||
|
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)
|
||||||
|
self.device.broadcast_media_mounted(self.device.working_directory)
|
||||||
|
|
||||||
|
def delete_assets(self):
|
||||||
|
for f in self.deployable_assets:
|
||||||
|
self.device.delete_file(self._path_on_device(f))
|
||||||
|
self.device.broadcast_media_mounted(self.device.working_directory)
|
||||||
|
|
||||||
|
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'] = 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):
|
||||||
"""
|
"""
|
||||||
@@ -392,6 +733,9 @@ class GameWorkload(ApkWorkload, ReventWorkload):
|
|||||||
|
|
||||||
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,
|
Parameter('clear_data_on_reset', kind=bool, default=True,
|
||||||
@@ -411,6 +755,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)
|
||||||
@@ -418,6 +764,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)
|
||||||
@@ -437,6 +787,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:
|
||||||
|
BIN
wlauto/common/bin/arm64/HelloJni.apk
Normal file
BIN
wlauto/common/bin/arm64/HelloJni.apk
Normal file
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/bin/arm64/m5
Executable file
BIN
wlauto/common/bin/arm64/m5
Executable file
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/bin/arm64/sqlite3
Normal file
BIN
wlauto/common/bin/arm64/sqlite3
Normal file
Binary file not shown.
BIN
wlauto/common/bin/armeabi/HelloJni.apk
Normal file
BIN
wlauto/common/bin/armeabi/HelloJni.apk
Normal file
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/bin/armeabi/m5
Executable file
BIN
wlauto/common/bin/armeabi/m5
Executable file
Binary file not shown.
Binary file not shown.
BIN
wlauto/common/bin/armeabi/sqlite3
Normal file
BIN
wlauto/common/bin/armeabi/sqlite3
Normal file
Binary file not shown.
6
wlauto/common/gem5/LICENSE
Normal file
6
wlauto/common/gem5/LICENSE
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
The gem5 simulator can be obtained from http://repo.gem5.org/gem5/ and the
|
||||||
|
corresponding documentation can be found at http://www.gem5.org.
|
||||||
|
|
||||||
|
The source for the m5 binaries bundled with Workload Automation (found at
|
||||||
|
wlauto/common/bin/arm64/m5 and wlauto/common/bin/armeabi/m5) can be found at
|
||||||
|
util/m5 in the gem5 source at http://repo.gem5.org/gem5/.
|
@@ -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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -26,9 +26,11 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
from pexpect import EOF, TIMEOUT
|
from pexpect import EOF, TIMEOUT, pxssh
|
||||||
|
|
||||||
from wlauto import settings, Parameter
|
from wlauto import settings, Parameter
|
||||||
|
from wlauto.core.resource import NO_ONE
|
||||||
|
from wlauto.common.resources import Executable
|
||||||
from wlauto.core import signal as sig
|
from wlauto.core import signal as sig
|
||||||
from wlauto.exceptions import DeviceError
|
from wlauto.exceptions import DeviceError
|
||||||
from wlauto.utils import ssh, types
|
from wlauto.utils import ssh, types
|
||||||
@@ -112,6 +114,7 @@ class BaseGem5Device(object):
|
|||||||
self.gem5 = None
|
self.gem5 = None
|
||||||
self.gem5_port = -1
|
self.gem5_port = -1
|
||||||
self.gem5outdir = os.path.join(settings.output_directory, "gem5")
|
self.gem5outdir = os.path.join(settings.output_directory, "gem5")
|
||||||
|
self.m5_path = 'm5'
|
||||||
self.stdout_file = None
|
self.stdout_file = None
|
||||||
self.stderr_file = None
|
self.stderr_file = None
|
||||||
self.stderr_filename = None
|
self.stderr_filename = None
|
||||||
@@ -238,9 +241,19 @@ class BaseGem5Device(object):
|
|||||||
port = self.gem5_port
|
port = self.gem5_port
|
||||||
|
|
||||||
# Connect to the gem5 telnet port. Use a short timeout here.
|
# Connect to the gem5 telnet port. Use a short timeout here.
|
||||||
self.sckt = ssh.TelnetConnection()
|
attempts = 0
|
||||||
self.sckt.login(host, 'None', port=port, auto_prompt_reset=False,
|
while attempts < 10:
|
||||||
login_timeout=10)
|
attempts += 1
|
||||||
|
try:
|
||||||
|
self.sckt = ssh.TelnetConnection()
|
||||||
|
self.sckt.login(host, 'None', port=port, auto_prompt_reset=False,
|
||||||
|
login_timeout=10)
|
||||||
|
break
|
||||||
|
except pxssh.ExceptionPxssh:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.gem5.kill()
|
||||||
|
raise DeviceError("Failed to connect to the gem5 telnet session.")
|
||||||
|
|
||||||
self.logger.info("Connected! Waiting for prompt...")
|
self.logger.info("Connected! Waiting for prompt...")
|
||||||
|
|
||||||
@@ -272,14 +285,7 @@ class BaseGem5Device(object):
|
|||||||
|
|
||||||
self.sckt.setecho(False)
|
self.sckt.setecho(False)
|
||||||
self.sync_gem5_shell()
|
self.sync_gem5_shell()
|
||||||
|
self.resize_shell()
|
||||||
# Try and avoid line wrapping as much as possible. Don't check the error
|
|
||||||
# codes from these command because some of them WILL fail.
|
|
||||||
self.gem5_shell('stty columns 1024', check_exit_code=False)
|
|
||||||
self.gem5_shell('busybox stty columns 1024', check_exit_code=False)
|
|
||||||
self.gem5_shell('stty cols 1024', check_exit_code=False)
|
|
||||||
self.gem5_shell('busybox stty cols 1024', check_exit_code=False)
|
|
||||||
self.gem5_shell('reset', check_exit_code=False)
|
|
||||||
|
|
||||||
def get_properties(self, context): # pylint: disable=R0801
|
def get_properties(self, context): # pylint: disable=R0801
|
||||||
""" Get the property files from the device """
|
""" Get the property files from the device """
|
||||||
@@ -323,7 +329,7 @@ class BaseGem5Device(object):
|
|||||||
|
|
||||||
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.gem5_shell('ps | busybox grep {}'.format(process_name),
|
result = self.gem5_shell('ps | {} grep {}'.format(self.busybox, process_name),
|
||||||
check_exit_code=False).strip()
|
check_exit_code=False).strip()
|
||||||
if result and 'not found' not in result and len(result.split('\n')) > 2:
|
if result and 'not found' not in result and len(result.split('\n')) > 2:
|
||||||
return [int(x.split()[1]) for x in result.split('\n')]
|
return [int(x.split()[1]) for x in result.split('\n')]
|
||||||
@@ -349,9 +355,6 @@ class BaseGem5Device(object):
|
|||||||
|
|
||||||
self.sckt.PROMPT = prompt
|
self.sckt.PROMPT = prompt
|
||||||
|
|
||||||
def login(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self._logcat_poller:
|
if self._logcat_poller:
|
||||||
self._logcat_poller.stop()
|
self._logcat_poller.stop()
|
||||||
@@ -360,10 +363,8 @@ class BaseGem5Device(object):
|
|||||||
self.logger.warn("Attempt to restart the gem5 device. This is not "
|
self.logger.warn("Attempt to restart the gem5 device. This is not "
|
||||||
"supported!")
|
"supported!")
|
||||||
|
|
||||||
def init(self):
|
# pylint: disable=unused-argument
|
||||||
pass
|
def push_file(self, source, dest, **kwargs):
|
||||||
|
|
||||||
def push_file(self, source, dest, _):
|
|
||||||
"""
|
"""
|
||||||
Push a file to the gem5 device using VirtIO
|
Push a file to the gem5 device using VirtIO
|
||||||
|
|
||||||
@@ -383,40 +384,34 @@ class BaseGem5Device(object):
|
|||||||
|
|
||||||
# Back to the gem5 world
|
# Back to the gem5 world
|
||||||
self.gem5_shell("ls -al /mnt/obb/{}".format(filename))
|
self.gem5_shell("ls -al /mnt/obb/{}".format(filename))
|
||||||
self.gem5_shell("busybox cp /mnt/obb/{} {}".format(filename, dest))
|
if self.busybox:
|
||||||
self.gem5_shell("busybox sync")
|
self.gem5_shell("{} cp /mnt/obb/{} {}".format(self.busybox, filename, dest))
|
||||||
|
else:
|
||||||
|
self.gem5_shell("cat /mnt/obb/{} > {}".format(filename, dest))
|
||||||
|
self.gem5_shell("sync")
|
||||||
self.gem5_shell("ls -al {}".format(dest))
|
self.gem5_shell("ls -al {}".format(dest))
|
||||||
self.gem5_shell("ls -al /mnt/obb/")
|
self.gem5_shell("ls -al /mnt/obb/")
|
||||||
self.logger.debug("Push complete.")
|
self.logger.debug("Push complete.")
|
||||||
|
|
||||||
def pull_file(self, source, dest, _):
|
# pylint: disable=unused-argument
|
||||||
|
def pull_file(self, source, dest, **kwargs):
|
||||||
"""
|
"""
|
||||||
Pull a file from the gem5 device using m5 writefile
|
Pull a file from the gem5 device using m5 writefile
|
||||||
|
|
||||||
First, we check the extension of the file to be copied. If the file ends
|
The file is copied to the local directory within the guest as the m5
|
||||||
in .gz, then gem5 wrongly assumes that it should create a gzipped output
|
|
||||||
stream, which results in a gem5 error. Therefore, we rename the file on
|
|
||||||
the local device prior to the writefile command when required. Next, the
|
|
||||||
file is copied to the local directory within the guest as the m5
|
|
||||||
writefile command assumes that the file is local. The file is then
|
writefile command assumes that the file is local. The file is then
|
||||||
written out to the host system using writefile, prior to being moved to
|
written out to the host system using writefile, prior to being moved to
|
||||||
the destination on the host.
|
the destination on the host.
|
||||||
"""
|
"""
|
||||||
filename = os.path.basename(source)
|
filename = os.path.basename(source)
|
||||||
self.logger.debug("Pulling {} from device.".format(filename))
|
|
||||||
|
|
||||||
# gem5 assumes that files ending in .gz are gzip-compressed. We need to
|
|
||||||
# work around this, else gem5 panics on us. Rename the file for use in
|
|
||||||
# gem5
|
|
||||||
if filename[-3:] == '.gz':
|
|
||||||
filename += '.fugem5'
|
|
||||||
|
|
||||||
self.logger.debug("pull_file {} {}".format(source, filename))
|
self.logger.debug("pull_file {} {}".format(source, filename))
|
||||||
# We don't check the exit code here because it is non-zero if the source
|
# We don't check the exit code here because it is non-zero if the source
|
||||||
# and destination are the same. The ls below will cause an error if the
|
# and destination are the same. The ls below will cause an error if the
|
||||||
# file was not where we expected it to be.
|
# file was not where we expected it to be.
|
||||||
self.gem5_shell("busybox cp {} {}".format(source, filename), check_exit_code=False)
|
self.gem5_shell("{} cp {} {}".format(self.busybox, source, filename),
|
||||||
self.gem5_shell("busybox sync")
|
check_exit_code=False)
|
||||||
|
self.gem5_shell("sync")
|
||||||
self.gem5_shell("ls -la {}".format(filename))
|
self.gem5_shell("ls -la {}".format(filename))
|
||||||
self.logger.debug('Finished the copy in the simulator')
|
self.logger.debug('Finished the copy in the simulator')
|
||||||
self.gem5_util("writefile {}".format(filename))
|
self.gem5_util("writefile {}".format(filename))
|
||||||
@@ -429,7 +424,8 @@ class BaseGem5Device(object):
|
|||||||
shutil.move(os.path.join(self.gem5outdir, filename), dest)
|
shutil.move(os.path.join(self.gem5outdir, filename), dest)
|
||||||
self.logger.debug("Pull complete.")
|
self.logger.debug("Pull complete.")
|
||||||
|
|
||||||
def delete_file(self, filepath, _):
|
# pylint: disable=unused-argument
|
||||||
|
def delete_file(self, filepath, **kwargs):
|
||||||
""" Delete a file on the device """
|
""" Delete a file on the device """
|
||||||
self._check_ready()
|
self._check_ready()
|
||||||
self.gem5_shell("rm '{}'".format(filepath))
|
self.gem5_shell("rm '{}'".format(filepath))
|
||||||
@@ -441,11 +437,10 @@ class BaseGem5Device(object):
|
|||||||
try:
|
try:
|
||||||
if int(output):
|
if int(output):
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# If we cannot process the output, assume that there is no file
|
# If we cannot process the output, assume that there is no file
|
||||||
return False
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
"""
|
"""
|
||||||
@@ -600,7 +595,7 @@ class BaseGem5Device(object):
|
|||||||
|
|
||||||
def gem5_util(self, command):
|
def gem5_util(self, command):
|
||||||
""" Execute a gem5 utility command using the m5 binary on the device """
|
""" Execute a gem5 utility command using the m5 binary on the device """
|
||||||
self.gem5_shell('/sbin/m5 ' + command)
|
self.gem5_shell('{} {}'.format(self.m5_path, command))
|
||||||
|
|
||||||
def sync_gem5_shell(self):
|
def sync_gem5_shell(self):
|
||||||
"""
|
"""
|
||||||
@@ -616,6 +611,19 @@ class BaseGem5Device(object):
|
|||||||
self.sckt.expect([self.sckt.UNIQUE_PROMPT, self.sckt.PROMPT], timeout=self.delay)
|
self.sckt.expect([self.sckt.UNIQUE_PROMPT, self.sckt.PROMPT], timeout=self.delay)
|
||||||
self.sckt.expect([self.sckt.UNIQUE_PROMPT, self.sckt.PROMPT], timeout=self.delay)
|
self.sckt.expect([self.sckt.UNIQUE_PROMPT, self.sckt.PROMPT], timeout=self.delay)
|
||||||
|
|
||||||
|
def resize_shell(self):
|
||||||
|
"""
|
||||||
|
Resize the shell to avoid line wrapping issues.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Try and avoid line wrapping as much as possible. Don't check the error
|
||||||
|
# codes from these command because some of them WILL fail.
|
||||||
|
self.gem5_shell('stty columns 1024', check_exit_code=False)
|
||||||
|
self.gem5_shell('{} stty columns 1024'.format(self.busybox), check_exit_code=False)
|
||||||
|
self.gem5_shell('stty cols 1024', check_exit_code=False)
|
||||||
|
self.gem5_shell('{} stty cols 1024'.format(self.busybox), check_exit_code=False)
|
||||||
|
self.gem5_shell('reset', check_exit_code=False)
|
||||||
|
|
||||||
def move_to_temp_dir(self, source):
|
def move_to_temp_dir(self, source):
|
||||||
"""
|
"""
|
||||||
Move a file to the temporary directory on the host for copying to the
|
Move a file to the temporary directory on the host for copying to the
|
||||||
@@ -639,10 +647,38 @@ class BaseGem5Device(object):
|
|||||||
"""
|
"""
|
||||||
self.logger.info("Mounting VirtIO device in simulated system")
|
self.logger.info("Mounting VirtIO device in simulated system")
|
||||||
|
|
||||||
self.gem5_shell('busybox mkdir -p /mnt/obb')
|
self.gem5_shell('mkdir -p /mnt/obb')
|
||||||
|
|
||||||
mount_command = "mount -t 9p -o trans=virtio,version=9p2000.L,aname={} gem5 /mnt/obb".format(self.temp_dir)
|
mount_command = "mount -t 9p -o trans=virtio,version=9p2000.L,aname={} gem5 /mnt/obb".format(self.temp_dir)
|
||||||
if self.platform == 'linux':
|
self.gem5_shell(mount_command)
|
||||||
self.gem5_shell(mount_command)
|
|
||||||
else:
|
def deploy_m5(self, context, force=False):
|
||||||
self.gem5_shell('busybox {}'.format(mount_command))
|
"""
|
||||||
|
Deploys the m5 binary to the device and returns the path to the binary
|
||||||
|
on the device.
|
||||||
|
|
||||||
|
:param force: by default, if the binary is already present on the
|
||||||
|
device, it will not be deployed again. Setting force to
|
||||||
|
``True`` overrides that behaviour and ensures that the
|
||||||
|
binary is always copied. Defaults to ``False``.
|
||||||
|
|
||||||
|
:returns: The on-device path to the m5 binary.
|
||||||
|
|
||||||
|
"""
|
||||||
|
on_device_executable = self.path.join(self.binaries_directory, 'm5')
|
||||||
|
if not force and self.file_exists(on_device_executable):
|
||||||
|
# We want to check the version of the binary. We cannot directly
|
||||||
|
# check this because the m5 binary itself is unversioned. We also
|
||||||
|
# need to make sure not to check the error code as "m5 --help"
|
||||||
|
# returns a non-zero error code.
|
||||||
|
output = self.gem5_shell('m5 --help', check_exit_code=False)
|
||||||
|
if "writefile" in output:
|
||||||
|
self.logger.debug("Using the m5 binary on the device...")
|
||||||
|
self.m5_path = on_device_executable
|
||||||
|
return on_device_executable
|
||||||
|
else:
|
||||||
|
self.logger.debug("m5 on device does not support writefile!")
|
||||||
|
host_file = context.resolver.get(Executable(NO_ONE, self.abi, 'm5'))
|
||||||
|
self.logger.info("Installing the m5 binary to the device...")
|
||||||
|
self.m5_path = self.install(host_file)
|
||||||
|
return self.m5_path
|
||||||
|
@@ -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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -37,6 +37,9 @@ FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (\S+) type (\S+) \((\S+)\)')
|
|||||||
|
|
||||||
FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
|
FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
|
||||||
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'])
|
||||||
|
|
||||||
|
GOOGLE_DNS_SERVER_ADDRESS = '8.8.8.8'
|
||||||
|
|
||||||
|
|
||||||
class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
||||||
@@ -89,6 +92,14 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
These paths do not have to exist and will be ignored if the path is not present on a
|
These paths do not have to exist and will be ignored if the path is not present on a
|
||||||
particular device.
|
particular device.
|
||||||
'''),
|
'''),
|
||||||
|
Parameter('binaries_directory',
|
||||||
|
description='Location of executable binaries on this device (must be in PATH).'),
|
||||||
|
Parameter('working_directory',
|
||||||
|
description='''
|
||||||
|
Working directory to be used by WA. This must be in a location where the specified user
|
||||||
|
has write permissions. This will default to /home/<username>/wa (or to /root/wa, if
|
||||||
|
username is 'root').
|
||||||
|
'''),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -125,6 +136,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')
|
||||||
@@ -183,11 +198,14 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
|
|
||||||
def initialize(self, context):
|
def initialize(self, context):
|
||||||
self.execute('mkdir -p {}'.format(self.working_directory))
|
self.execute('mkdir -p {}'.format(self.working_directory))
|
||||||
if self.is_rooted:
|
if not self.binaries_directory:
|
||||||
if not self.is_installed('busybox'):
|
self._set_binaries_dir()
|
||||||
self.busybox = self.deploy_busybox(context)
|
self.execute('mkdir -p {}'.format(self.binaries_directory))
|
||||||
else:
|
self.busybox = self.deploy_busybox(context)
|
||||||
self.busybox = 'busybox'
|
|
||||||
|
def _set_binaries_dir(self):
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
self.binaries_directory = self.path.join(self.working_directory, "bin")
|
||||||
|
|
||||||
def is_file(self, filepath):
|
def is_file(self, filepath):
|
||||||
output = self.execute('if [ -f \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
|
output = self.execute('if [ -f \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
|
||||||
@@ -208,7 +226,10 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
outfile = os.path.join(context.host_working_directory, normname)
|
outfile = os.path.join(context.host_working_directory, normname)
|
||||||
if self.is_file(propfile):
|
if self.is_file(propfile):
|
||||||
with open(outfile, 'w') as wfh:
|
with open(outfile, 'w') as wfh:
|
||||||
wfh.write(self.execute('cat {}'.format(propfile)))
|
if propfile.endswith(".gz"):
|
||||||
|
wfh.write(self.execute('{} zcat {}'.format(self.busybox, propfile)))
|
||||||
|
else:
|
||||||
|
wfh.write(self.execute('cat {}'.format(propfile)))
|
||||||
elif self.is_directory(propfile):
|
elif self.is_directory(propfile):
|
||||||
self.pull_file(propfile, outfile)
|
self.pull_file(propfile, outfile)
|
||||||
else:
|
else:
|
||||||
@@ -248,7 +269,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
self.execute('echo {} > \'{}\''.format(value, sysfile), check_exit_code=False, as_root=True)
|
self.execute('echo {} > \'{}\''.format(value, sysfile), check_exit_code=False, as_root=True)
|
||||||
if verify:
|
if verify:
|
||||||
output = self.get_sysfile_value(sysfile)
|
output = self.get_sysfile_value(sysfile)
|
||||||
if not 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)
|
||||||
@@ -281,7 +302,6 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
Deploys the busybox binary to the specified device and returns
|
Deploys the busybox binary to the specified device and returns
|
||||||
the path to the binary on the device.
|
the path to the binary on the device.
|
||||||
|
|
||||||
:param device: device to deploy the binary to.
|
|
||||||
:param context: an instance of ExecutionContext
|
:param context: an instance of ExecutionContext
|
||||||
:param force: by default, if the binary is already present on the
|
:param force: by default, if the binary is already present on the
|
||||||
device, it will not be deployed again. Setting force
|
device, it will not be deployed again. Setting force
|
||||||
@@ -291,16 +311,92 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
:returns: The on-device path to the busybox binary.
|
:returns: The on-device path to the busybox binary.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
on_device_executable = self.path.join(self.binaries_directory, 'busybox')
|
on_device_executable = self.get_binary_path("busybox", search_system_binaries=False)
|
||||||
if not force and self.file_exists(on_device_executable):
|
if force or not on_device_executable:
|
||||||
return on_device_executable
|
host_file = context.resolver.get(Executable(NO_ONE, self.abi, 'busybox'))
|
||||||
host_file = context.resolver.get(Executable(NO_ONE, self.abi, 'busybox'))
|
return self.install(host_file)
|
||||||
return self.install(host_file)
|
return on_device_executable
|
||||||
|
|
||||||
|
def is_installed(self, name): # pylint: disable=unused-argument,no-self-use
|
||||||
|
raise AttributeError("""Instead of using is_installed, please use
|
||||||
|
``get_binary_path`` or ``install_if_needed`` instead. You should
|
||||||
|
use the path returned by these functions to then invoke the binary
|
||||||
|
|
||||||
|
please see: https://pythonhosted.org/wlauto/writing_extensions.html""")
|
||||||
|
|
||||||
|
def is_network_connected(self):
|
||||||
|
"""
|
||||||
|
Checks for internet connectivity on the device by pinging IP address provided.
|
||||||
|
|
||||||
|
:param ip_address: IP address to ping. Default is Google's public DNS server (8.8.8.8)
|
||||||
|
|
||||||
|
:returns: ``True`` if internet is available, ``False`` otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.logger.debug('Checking for internet connectivity...')
|
||||||
|
return self._ping_server(GOOGLE_DNS_SERVER_ADDRESS)
|
||||||
|
|
||||||
|
def _ping_server(self, ip_address, timeout=1, packet_count=1):
|
||||||
|
output = self.execute('ping -q -c {} -w {} {}'.format(packet_count, timeout, ip_address),
|
||||||
|
check_exit_code=False)
|
||||||
|
|
||||||
|
if 'network is unreachable' in output.lower():
|
||||||
|
self.logger.debug('Cannot find IP address {}'.format(ip_address))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.logger.debug('Found IP address {}'.format(ip_address))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_binary_path(self, name, search_system_binaries=True):
|
||||||
|
"""
|
||||||
|
Searches the devices ``binary_directory`` for the given binary,
|
||||||
|
if it cant find it there it tries using which to find it.
|
||||||
|
|
||||||
|
:param name: The name of the binary
|
||||||
|
:param search_system_binaries: By default this function will try using
|
||||||
|
which to find the binary if it isn't in
|
||||||
|
``binary_directory``. When this is set
|
||||||
|
to ``False`` it will not try this.
|
||||||
|
|
||||||
|
:returns: The on-device path to the binary.
|
||||||
|
|
||||||
|
"""
|
||||||
|
wa_binary = self.path.join(self.binaries_directory, name)
|
||||||
|
if self.file_exists(wa_binary):
|
||||||
|
return wa_binary
|
||||||
|
if search_system_binaries:
|
||||||
|
try:
|
||||||
|
return self.execute('{} which {}'.format(self.busybox, name)).strip()
|
||||||
|
except DeviceError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def install_if_needed(self, host_path, search_system_binaries=True):
|
||||||
|
"""
|
||||||
|
Similar to get_binary_path but will install the binary if not found.
|
||||||
|
|
||||||
|
:param host_path: The path to the binary on the host
|
||||||
|
:param search_system_binaries: By default this function will try using
|
||||||
|
which to find the binary if it isn't in
|
||||||
|
``binary_directory``. When this is set
|
||||||
|
to ``False`` it will not try this.
|
||||||
|
|
||||||
|
:returns: The on-device path to the binary.
|
||||||
|
|
||||||
|
"""
|
||||||
|
binary_path = self.get_binary_path(os.path.split(host_path)[1],
|
||||||
|
search_system_binaries=search_system_binaries)
|
||||||
|
if not binary_path:
|
||||||
|
binary_path = self.install(host_path)
|
||||||
|
return binary_path
|
||||||
|
|
||||||
def list_file_systems(self):
|
def list_file_systems(self):
|
||||||
output = self.execute('mount')
|
output = self.execute('mount')
|
||||||
fstab = []
|
fstab = []
|
||||||
for line in output.split('\n'):
|
for line in output.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
match = FSTAB_ENTRY_REGEX.search(line)
|
match = FSTAB_ENTRY_REGEX.search(line)
|
||||||
if match:
|
if match:
|
||||||
fstab.append(FstabEntry(match.group(1), match.group(2),
|
fstab.append(FstabEntry(match.group(1), match.group(2),
|
||||||
@@ -332,7 +428,7 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
signal_string = '-s {}'.format(signal) if signal else ''
|
signal_string = '-s {}'.format(signal) if signal else ''
|
||||||
self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
|
self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
|
||||||
|
|
||||||
def killall(self, process_name, signal=None, as_root=False): # pylint: disable=W0221
|
def killall(self, process_name, signal=None, as_root=None): # pylint: disable=W0221
|
||||||
"""
|
"""
|
||||||
Kill all processes with the specified name.
|
Kill all processes with the specified name.
|
||||||
|
|
||||||
@@ -343,6 +439,8 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
Modified in version 2.1.5: added ``as_root`` parameter.
|
Modified in version 2.1.5: added ``as_root`` parameter.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if as_root is None:
|
||||||
|
as_root = self.is_rooted
|
||||||
for pid in self.get_pids_of(process_name):
|
for pid in self.get_pids_of(process_name):
|
||||||
self.kill(pid, signal=signal, as_root=as_root)
|
self.kill(pid, signal=signal, as_root=as_root)
|
||||||
|
|
||||||
@@ -473,12 +571,20 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method
|
|||||||
if isinstance(on_cpus, basestring):
|
if isinstance(on_cpus, basestring):
|
||||||
on_cpus = ranges_to_list(on_cpus)
|
on_cpus = ranges_to_list(on_cpus)
|
||||||
if isiterable(on_cpus):
|
if isiterable(on_cpus):
|
||||||
on_cpus = list_to_mask(on_cpus)
|
on_cpus = list_to_mask(on_cpus) # pylint: disable=redefined-variable-type
|
||||||
command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
|
command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
|
||||||
if in_directory:
|
if in_directory:
|
||||||
command = 'cd {} && {}'.format(in_directory, command)
|
command = 'cd {} && {}'.format(in_directory, command)
|
||||||
return self.execute(command, background=background, as_root=as_root, timeout=timeout)
|
return self.execute(command, background=background, as_root=as_root, timeout=timeout)
|
||||||
|
|
||||||
|
def get_device_model(self):
|
||||||
|
if self.file_exists("/proc/device-tree/model"):
|
||||||
|
raw_model = self.execute("cat /proc/device-tree/model")
|
||||||
|
return '_'.join(raw_model.split()[:2])
|
||||||
|
# Right now we don't know any other way to get device model
|
||||||
|
# info in linux on arm platforms
|
||||||
|
return None
|
||||||
|
|
||||||
# internal methods
|
# internal methods
|
||||||
|
|
||||||
def _check_ready(self):
|
def _check_ready(self):
|
||||||
@@ -517,19 +623,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').
|
|
||||||
'''),
|
|
||||||
Parameter('binaries_directory', default='/usr/local/bin',
|
|
||||||
description='Location of executable binaries on this device (must be in PATH).'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@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:
|
||||||
@@ -551,7 +649,6 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(LinuxDevice, self).__init__(*args, **kwargs)
|
super(LinuxDevice, self).__init__(*args, **kwargs)
|
||||||
self.shell = None
|
self.shell = None
|
||||||
self.local_binaries_directory = None
|
|
||||||
self._is_rooted = None
|
self._is_rooted = None
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -560,19 +657,20 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
self.working_directory = '/root/wa' # pylint: disable=attribute-defined-outside-init
|
self.working_directory = '/root/wa' # pylint: disable=attribute-defined-outside-init
|
||||||
else:
|
else:
|
||||||
self.working_directory = '/home/{}/wa'.format(self.username) # pylint: disable=attribute-defined-outside-init
|
self.working_directory = '/home/{}/wa'.format(self.username) # pylint: disable=attribute-defined-outside-init
|
||||||
self.local_binaries_directory = self.path.join(self.working_directory, 'bin')
|
|
||||||
|
|
||||||
def initialize(self, context, *args, **kwargs):
|
def initialize(self, context, *args, **kwargs):
|
||||||
self.execute('mkdir -p {}'.format(self.local_binaries_directory))
|
|
||||||
self.execute('mkdir -p {}'.format(self.binaries_directory))
|
self.execute('mkdir -p {}'.format(self.binaries_directory))
|
||||||
self.execute('export PATH={}:$PATH'.format(self.local_binaries_directory))
|
|
||||||
self.execute('export PATH={}:$PATH'.format(self.binaries_directory))
|
self.execute('export PATH={}:$PATH'.format(self.binaries_directory))
|
||||||
super(LinuxDevice, self).initialize(context, *args, **kwargs)
|
super(LinuxDevice, self).initialize(context, *args, **kwargs)
|
||||||
|
|
||||||
# Power control
|
# Power control
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.execute('reboot', as_root=True)
|
try:
|
||||||
|
self.execute('reboot', as_root=True)
|
||||||
|
except DeviceError as e:
|
||||||
|
if 'Connection dropped' not in e.message:
|
||||||
|
raise e
|
||||||
self._is_ready = False
|
self._is_ready = False
|
||||||
|
|
||||||
def hard_reset(self):
|
def hard_reset(self):
|
||||||
@@ -584,8 +682,15 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
else:
|
else:
|
||||||
self.reset()
|
self.reset()
|
||||||
self.logger.debug('Waiting for device...')
|
self.logger.debug('Waiting for device...')
|
||||||
|
# Wait a fixed delay before starting polling to give the device time to
|
||||||
|
# shut down, otherwise, might create the connection while it's still shutting
|
||||||
|
# down resulting in subsequenct connection failing.
|
||||||
|
initial_delay = 20
|
||||||
|
time.sleep(initial_delay)
|
||||||
|
boot_timeout = max(self.boot_timeout - initial_delay, 10)
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
while (time.time() - start_time) < self.boot_timeout:
|
while (time.time() - start_time) < boot_timeout:
|
||||||
try:
|
try:
|
||||||
s = socket.create_connection((self.host, self.port), timeout=5)
|
s = socket.create_connection((self.host, self.port), timeout=5)
|
||||||
s.close()
|
s.close()
|
||||||
@@ -609,15 +714,6 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
|
|
||||||
# Execution
|
# Execution
|
||||||
|
|
||||||
def has_root(self):
|
|
||||||
try:
|
|
||||||
self.execute('ls /', as_root=True)
|
|
||||||
return True
|
|
||||||
except DeviceError as e:
|
|
||||||
if 'not in the sudoers file' not in e.message:
|
|
||||||
raise e
|
|
||||||
return False
|
|
||||||
|
|
||||||
def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False,
|
def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False,
|
||||||
as_root=False, strip_colors=True, **kwargs):
|
as_root=False, strip_colors=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -660,16 +756,18 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
raise DeviceError(e)
|
raise DeviceError(e)
|
||||||
|
|
||||||
def kick_off(self, command):
|
def kick_off(self, command, as_root=None):
|
||||||
"""
|
"""
|
||||||
Like execute but closes adb session and returns immediately, leaving the command running on the
|
Like execute but closes ssh session and returns immediately, leaving the command running on the
|
||||||
device (this is different from execute(background=True) which keeps adb connection open and returns
|
device (this is different from execute(background=True) which keeps ssh connection open and returns
|
||||||
a subprocess object).
|
a subprocess object).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if as_root is None:
|
||||||
|
as_root = self.is_rooted
|
||||||
self._check_ready()
|
self._check_ready()
|
||||||
command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
|
command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
|
||||||
return self.shell.execute(command)
|
return self.shell.execute(command, as_root=as_root)
|
||||||
|
|
||||||
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."""
|
||||||
@@ -744,39 +842,48 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
return [x.strip() for x in contents.split('\n')] # pylint: disable=maybe-no-member
|
return [x.strip() for x in contents.split('\n')] # pylint: disable=maybe-no-member
|
||||||
|
|
||||||
def install(self, filepath, timeout=default_timeout, with_name=None): # pylint: disable=W0221
|
def install(self, filepath, timeout=default_timeout, with_name=None): # pylint: disable=W0221
|
||||||
if self.is_rooted:
|
destpath = self.path.join(self.binaries_directory,
|
||||||
destpath = self.path.join(self.binaries_directory,
|
with_name or self.path.basename(filepath))
|
||||||
with_name and with_name or self.path.basename(filepath))
|
self.push_file(filepath, destpath, as_root=True)
|
||||||
self.push_file(filepath, destpath, as_root=True)
|
self.execute('chmod a+x {}'.format(destpath), timeout=timeout, as_root=True)
|
||||||
self.execute('chmod a+x {}'.format(destpath), timeout=timeout, as_root=True)
|
|
||||||
else:
|
|
||||||
destpath = self.path.join(self.local_binaries_directory,
|
|
||||||
with_name and with_name or self.path.basename(filepath))
|
|
||||||
self.push_file(filepath, destpath)
|
|
||||||
self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
|
|
||||||
return destpath
|
return destpath
|
||||||
|
|
||||||
install_executable = install # compatibility
|
install_executable = install # compatibility
|
||||||
|
|
||||||
def uninstall(self, name):
|
def uninstall(self, executable_name):
|
||||||
if self.is_rooted:
|
on_device_executable = self.get_binary_path(executable_name, search_system_binaries=False)
|
||||||
path = self.path.join(self.binaries_directory, name)
|
if not on_device_executable:
|
||||||
self.delete_file(path, as_root=True)
|
raise DeviceError("Could not uninstall {}, binary not found".format(on_device_executable))
|
||||||
else:
|
self.delete_file(on_device_executable, as_root=self.is_rooted)
|
||||||
path = self.path.join(self.local_binaries_directory, name)
|
|
||||||
self.delete_file(path)
|
|
||||||
|
|
||||||
uninstall_executable = uninstall # compatibility
|
uninstall_executable = uninstall # compatibility
|
||||||
|
|
||||||
def is_installed(self, name):
|
|
||||||
try:
|
|
||||||
self.execute('which {}'.format(name))
|
|
||||||
return True
|
|
||||||
except DeviceError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
|
||||||
|
def lsmod(self):
|
||||||
|
"""List loaded kernel modules."""
|
||||||
|
lines = self.execute('lsmod').splitlines()
|
||||||
|
entries = []
|
||||||
|
for line in lines[1:]: # first line is the header
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
parts = line.split()
|
||||||
|
name = parts[0]
|
||||||
|
size = int(parts[1])
|
||||||
|
use_count = int(parts[2])
|
||||||
|
if len(parts) > 3:
|
||||||
|
used_by = ''.join(parts[3:]).split(',')
|
||||||
|
else:
|
||||||
|
used_by = []
|
||||||
|
entries.append(LsmodEntry(name, size, use_count, used_by))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def insmod(self, path):
|
||||||
|
"""Install a kernel module located on the host on the target device."""
|
||||||
|
target_path = self.path.join(self.working_directory, os.path.basename(path))
|
||||||
|
self.push_file(path, target_path)
|
||||||
|
self.execute('insmod {}'.format(target_path), as_root=True)
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
try:
|
try:
|
||||||
# May be triggered inside initialize()
|
# May be triggered inside initialize()
|
||||||
@@ -785,7 +892,7 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
raise DeviceNotRespondingError(self.host)
|
raise DeviceNotRespondingError(self.host)
|
||||||
|
|
||||||
def capture_screen(self, filepath):
|
def capture_screen(self, filepath):
|
||||||
if not self.is_installed('scrot'):
|
if not self.get_binary_path('scrot'):
|
||||||
self.logger.debug('Could not take screenshot as scrot is not installed.')
|
self.logger.debug('Could not take screenshot as scrot is not installed.')
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -804,4 +911,3 @@ class LinuxDevice(BaseLinuxDevice):
|
|||||||
|
|
||||||
def ensure_screen_is_on(self):
|
def ensure_screen_is_on(self):
|
||||||
pass # TODO
|
pass # TODO
|
||||||
|
|
||||||
|
@@ -54,6 +54,9 @@ 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 ##########################################
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
@@ -84,7 +87,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 +192,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 +225,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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -16,13 +16,12 @@
|
|||||||
import os
|
import os
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
import yaml
|
||||||
|
|
||||||
from wlauto.exceptions import ConfigError
|
from wlauto.exceptions import ConfigError
|
||||||
from wlauto.utils.misc import load_struct_from_yaml, LoadSyntaxError
|
from wlauto.utils.misc import load_struct_from_yaml, LoadSyntaxError
|
||||||
from wlauto.utils.types import counter, reset_counter
|
from wlauto.utils.types import counter, reset_counter
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
def get_aliased_param(d, aliases, default=None, pop=True):
|
def get_aliased_param(d, aliases, default=None, pop=True):
|
||||||
alias_map = [i for i, a in enumerate(aliases) if a in d]
|
alias_map = [i for i, a in enumerate(aliases) if a in d]
|
||||||
@@ -174,6 +173,9 @@ class Agenda(object):
|
|||||||
message = '{} does not contain a valid agenda structure; top level must be a dict.'
|
message = '{} does not contain a valid agenda structure; top level must be a dict.'
|
||||||
raise ConfigError(message.format(self.filepath))
|
raise ConfigError(message.format(self.filepath))
|
||||||
for k, v in raw.iteritems():
|
for k, v in raw.iteritems():
|
||||||
|
if v is None:
|
||||||
|
raise ConfigError('Empty "{}" entry in {}'.format(k, self.filepath))
|
||||||
|
|
||||||
if k == 'config':
|
if k == 'config':
|
||||||
if not isinstance(v, dict):
|
if not isinstance(v, dict):
|
||||||
raise ConfigError('Invalid agenda: "config" entry must be a dict')
|
raise ConfigError('Invalid agenda: "config" entry must be a dict')
|
||||||
|
@@ -114,8 +114,8 @@ class ConfigLoader(object):
|
|||||||
new_config = load_struct_from_yaml(source)
|
new_config = load_struct_from_yaml(source)
|
||||||
else:
|
else:
|
||||||
raise ConfigError('Unknown config format: {}'.format(source))
|
raise ConfigError('Unknown config format: {}'.format(source))
|
||||||
except LoadSyntaxError as e:
|
except (LoadSyntaxError, ValueError) as e:
|
||||||
raise ConfigError(e)
|
raise ConfigError('Invalid config "{}":\n\t{}'.format(source, e))
|
||||||
|
|
||||||
self._config = merge_dicts(self._config, new_config,
|
self._config = merge_dicts(self._config, new_config,
|
||||||
list_duplicates='first',
|
list_duplicates='first',
|
||||||
@@ -211,4 +211,3 @@ if os.path.isfile(_packages_file):
|
|||||||
|
|
||||||
for config in _env_configs:
|
for config in _env_configs:
|
||||||
settings.update(config)
|
settings.update(config)
|
||||||
|
|
||||||
|
@@ -425,7 +425,7 @@ class RunConfiguration(object):
|
|||||||
is validated (to make sure there are no missing settings, etc).
|
is validated (to make sure there are no missing settings, etc).
|
||||||
- Extensions are loaded through the run config object, which instantiates
|
- Extensions are loaded through the run config object, which instantiates
|
||||||
them with appropriate parameters based on the "raw" config collected earlier. When an
|
them with appropriate parameters based on the "raw" config collected earlier. When an
|
||||||
Extension is instantiated in such a way, it's config is "officially" added to run configuration
|
Extension is instantiated in such a way, its config is "officially" added to run configuration
|
||||||
tracked by the run config object. Raw config is discarded at the end of the run, so
|
tracked by the run config object. Raw config is discarded at the end of the run, so
|
||||||
that any config that wasn't loaded in this way is not recoded (as it was not actually used).
|
that any config that wasn't loaded in this way is not recoded (as it was not actually used).
|
||||||
- Extension parameters a validated individually (for type, value ranges, etc) as they are
|
- Extension parameters a validated individually (for type, value ranges, etc) as they are
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
@@ -174,7 +174,7 @@ class Device(Extension):
|
|||||||
description="""
|
description="""
|
||||||
This is a list indicating the cluster affinity of the CPU cores,
|
This is a list indicating the cluster affinity of the CPU cores,
|
||||||
each element correponding to the cluster ID of the core coresponding
|
each element correponding to the cluster ID of the core coresponding
|
||||||
to it's index. E.g. ``[0, 0, 1]`` indicates that cpu0 and cpu1 are on
|
to its index. E.g. ``[0, 0, 1]`` indicates that cpu0 and cpu1 are on
|
||||||
cluster 0, while cpu2 is on cluster 1. If this is not specified, this
|
cluster 0, while cpu2 is on cluster 1. If this is not specified, this
|
||||||
will be inferred from ``core_names`` if possible (assuming all cores with
|
will be inferred from ``core_names`` if possible (assuming all cores with
|
||||||
the same name are on the same cluster).
|
the same name are on the same cluster).
|
||||||
@@ -303,7 +303,7 @@ class Device(Extension):
|
|||||||
params = OrderedDict((k.lower(), v) for k, v in params.iteritems() if v is not None)
|
params = OrderedDict((k.lower(), v) for k, v in params.iteritems() if v is not None)
|
||||||
|
|
||||||
expected_keys = rtp_map.keys()
|
expected_keys = rtp_map.keys()
|
||||||
if not set(params.keys()) <= set(expected_keys):
|
if not set(params.keys()).issubset(set(expected_keys)):
|
||||||
unknown_params = list(set(params.keys()).difference(set(expected_keys)))
|
unknown_params = list(set(params.keys()).difference(set(expected_keys)))
|
||||||
raise ConfigError('Unknown runtime parameter(s): {}'.format(unknown_params))
|
raise ConfigError('Unknown runtime parameter(s): {}'.format(unknown_params))
|
||||||
|
|
||||||
@@ -426,6 +426,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)
|
||||||
|
|
||||||
@@ -447,4 +454,3 @@ class Device(Extension):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.ping()
|
self.ping()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
@@ -17,18 +17,19 @@
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import warnings
|
||||||
|
|
||||||
from wlauto.core.bootstrap import settings
|
from wlauto.core.bootstrap import settings
|
||||||
from wlauto.core.extension_loader import ExtensionLoader
|
from wlauto.core.extension_loader import ExtensionLoader
|
||||||
from wlauto.exceptions import WAError
|
from wlauto.exceptions import WAError, ConfigError
|
||||||
from wlauto.utils.misc import get_traceback
|
from wlauto.utils.misc import get_traceback
|
||||||
from wlauto.utils.log import init_logging
|
from wlauto.utils.log import init_logging
|
||||||
from wlauto.utils.cli import init_argument_parser
|
from wlauto.utils.cli import init_argument_parser
|
||||||
from wlauto.utils.doc import format_body
|
from wlauto.utils.doc import format_body
|
||||||
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
warnings.filterwarnings(action='ignore', category=UserWarning, module='zope')
|
warnings.filterwarnings(action='ignore', category=UserWarning, module='zope')
|
||||||
|
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ def main():
|
|||||||
settings.verbosity = args.verbose
|
settings.verbosity = args.verbose
|
||||||
settings.debug = args.debug
|
settings.debug = args.debug
|
||||||
if args.config:
|
if args.config:
|
||||||
|
if not os.path.exists(args.config):
|
||||||
|
raise ConfigError("Config file {} not found".format(args.config))
|
||||||
settings.update(args.config)
|
settings.update(args.config)
|
||||||
init_logging(settings.verbosity)
|
init_logging(settings.verbosity)
|
||||||
|
|
||||||
@@ -89,4 +92,3 @@ def main():
|
|||||||
logging.critical(tb)
|
logging.critical(tb)
|
||||||
logging.critical('{}({})'.format(e.__class__.__name__, e))
|
logging.critical('{}({})'.format(e.__class__.__name__, e))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ REBOOT_DELAY = 3
|
|||||||
|
|
||||||
class RunInfo(object):
|
class RunInfo(object):
|
||||||
"""
|
"""
|
||||||
Information about the current run, such as it's unique ID, run
|
Information about the current run, such as its unique ID, run
|
||||||
time, etc.
|
time, etc.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -85,7 +86,8 @@ class RunInfo(object):
|
|||||||
self.duration = None
|
self.duration = None
|
||||||
self.project = config.project
|
self.project = config.project
|
||||||
self.project_stage = config.project_stage
|
self.project_stage = config.project_stage
|
||||||
self.run_name = config.run_name
|
self.run_name = config.run_name or "{}_{}".format(os.path.split(settings.output_directory)[1],
|
||||||
|
datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S"))
|
||||||
self.notes = None
|
self.notes = None
|
||||||
self.device_properties = {}
|
self.device_properties = {}
|
||||||
|
|
||||||
@@ -203,6 +205,9 @@ class ExecutionContext(object):
|
|||||||
def add_metric(self, *args, **kwargs):
|
def add_metric(self, *args, **kwargs):
|
||||||
self.result.add_metric(*args, **kwargs)
|
self.result.add_metric(*args, **kwargs)
|
||||||
|
|
||||||
|
def add_classifiers(self, **kwargs):
|
||||||
|
self.result.classifiers.update(kwargs)
|
||||||
|
|
||||||
def add_artifact(self, name, path, kind, *args, **kwargs):
|
def add_artifact(self, name, path, kind, *args, **kwargs):
|
||||||
if self.current_job is None:
|
if self.current_job is None:
|
||||||
self.add_run_artifact(name, path, kind, *args, **kwargs)
|
self.add_run_artifact(name, path, kind, *args, **kwargs)
|
||||||
@@ -340,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):
|
||||||
@@ -727,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:
|
||||||
@@ -740,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:
|
||||||
|
@@ -41,9 +41,10 @@ class AttributeCollection(object):
|
|||||||
def values(self):
|
def values(self):
|
||||||
return self._attrs.values()
|
return self._attrs.values()
|
||||||
|
|
||||||
def __init__(self, attrcls):
|
def __init__(self, attrcls, owner):
|
||||||
self._attrcls = attrcls
|
self._attrcls = attrcls
|
||||||
self._attrs = OrderedDict()
|
self._attrs = OrderedDict()
|
||||||
|
self.owner = owner
|
||||||
|
|
||||||
def add(self, p):
|
def add(self, p):
|
||||||
p = self._to_attrcls(p)
|
p = self._to_attrcls(p)
|
||||||
@@ -53,6 +54,8 @@ class AttributeCollection(object):
|
|||||||
for a, v in p.__dict__.iteritems():
|
for a, v in p.__dict__.iteritems():
|
||||||
if v is not None:
|
if v is not None:
|
||||||
setattr(newp, a, v)
|
setattr(newp, a, v)
|
||||||
|
if not hasattr(newp, "_overridden"):
|
||||||
|
newp._overridden = self.owner # pylint: disable=protected-access
|
||||||
self._attrs[p.name] = newp
|
self._attrs[p.name] = newp
|
||||||
else:
|
else:
|
||||||
# Duplicate attribute condition is check elsewhere.
|
# Duplicate attribute condition is check elsewhere.
|
||||||
@@ -82,7 +85,12 @@ class AttributeCollection(object):
|
|||||||
return p
|
return p
|
||||||
|
|
||||||
def __iadd__(self, other):
|
def __iadd__(self, other):
|
||||||
|
other = [self._to_attrcls(p) for p in other]
|
||||||
|
names = []
|
||||||
for p in other:
|
for p in other:
|
||||||
|
if p.name in names:
|
||||||
|
raise ValueError("Duplicate '{}' {}".format(p.name, p.__class__.__name__.split('.')[-1]))
|
||||||
|
names.append(p.name)
|
||||||
self.add(p)
|
self.add(p)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -102,7 +110,7 @@ class AttributeCollection(object):
|
|||||||
class AliasCollection(AttributeCollection):
|
class AliasCollection(AttributeCollection):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(AliasCollection, self).__init__(Alias)
|
super(AliasCollection, self).__init__(Alias, None)
|
||||||
|
|
||||||
def _to_attrcls(self, p):
|
def _to_attrcls(self, p):
|
||||||
if isinstance(p, tuple) or isinstance(p, list):
|
if isinstance(p, tuple) or isinstance(p, list):
|
||||||
@@ -117,8 +125,9 @@ class AliasCollection(AttributeCollection):
|
|||||||
|
|
||||||
class ListCollection(list):
|
class ListCollection(list):
|
||||||
|
|
||||||
def __init__(self, attrcls): # pylint: disable=unused-argument
|
def __init__(self, attrcls, owner): # pylint: disable=unused-argument
|
||||||
super(ListCollection, self).__init__()
|
super(ListCollection, self).__init__()
|
||||||
|
self.owner = owner
|
||||||
|
|
||||||
|
|
||||||
class Param(object):
|
class Param(object):
|
||||||
@@ -215,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)
|
||||||
@@ -303,7 +305,7 @@ class Artifact(object):
|
|||||||
network filer archiver may choose to archive them).
|
network filer archiver may choose to archive them).
|
||||||
|
|
||||||
.. note: The kind parameter is intended to represent the logical function of a particular
|
.. note: The kind parameter is intended to represent the logical function of a particular
|
||||||
artifact, not it's intended means of processing -- this is left entirely up to the
|
artifact, not its intended means of processing -- this is left entirely up to the
|
||||||
result processors.
|
result processors.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -396,31 +398,38 @@ class ExtensionMeta(type):
|
|||||||
global_virtuals = ['initialize', 'finalize']
|
global_virtuals = ['initialize', 'finalize']
|
||||||
|
|
||||||
def __new__(mcs, clsname, bases, attrs):
|
def __new__(mcs, clsname, bases, attrs):
|
||||||
mcs._propagate_attributes(bases, attrs)
|
mcs._propagate_attributes(bases, attrs, clsname)
|
||||||
cls = type.__new__(mcs, clsname, bases, attrs)
|
cls = type.__new__(mcs, clsname, bases, attrs)
|
||||||
mcs._setup_aliases(cls)
|
mcs._setup_aliases(cls)
|
||||||
mcs._implement_virtual(cls, bases)
|
mcs._implement_virtual(cls, bases)
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _propagate_attributes(mcs, bases, attrs):
|
def _propagate_attributes(mcs, bases, attrs, clsname):
|
||||||
"""
|
"""
|
||||||
For attributes specified by to_propagate, their values will be a union of
|
For attributes specified by to_propagate, their values will be a union of
|
||||||
that specified for cls and it's bases (cls values overriding those of bases
|
that specified for cls and its bases (cls values overriding those of bases
|
||||||
in case of conflicts).
|
in case of conflicts).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for prop_attr, attr_cls, attr_collector_cls in mcs.to_propagate:
|
for prop_attr, attr_cls, attr_collector_cls in mcs.to_propagate:
|
||||||
should_propagate = False
|
should_propagate = False
|
||||||
propagated = attr_collector_cls(attr_cls)
|
propagated = attr_collector_cls(attr_cls, clsname)
|
||||||
for base in bases:
|
for base in bases:
|
||||||
if hasattr(base, prop_attr):
|
if hasattr(base, prop_attr):
|
||||||
propagated += getattr(base, prop_attr) or []
|
propagated += getattr(base, prop_attr) or []
|
||||||
should_propagate = True
|
should_propagate = True
|
||||||
if prop_attr in attrs:
|
if prop_attr in attrs:
|
||||||
propagated += attrs[prop_attr] or []
|
pattrs = attrs[prop_attr] or []
|
||||||
|
propagated += pattrs
|
||||||
should_propagate = True
|
should_propagate = True
|
||||||
if should_propagate:
|
if should_propagate:
|
||||||
|
for p in propagated:
|
||||||
|
override = bool(getattr(p, "override", None))
|
||||||
|
overridden = bool(getattr(p, "_overridden", None))
|
||||||
|
if override != overridden:
|
||||||
|
msg = "Overriding non existing parameter '{}' inside '{}'"
|
||||||
|
raise ValueError(msg.format(p.name, clsname))
|
||||||
attrs[prop_attr] = propagated
|
attrs[prop_attr] = propagated
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -551,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
|
||||||
@@ -665,7 +676,7 @@ class Module(Extension):
|
|||||||
In other words, a Module is roughly equivalent to a kernel module and its primary purpose is to
|
In other words, a Module is roughly equivalent to a kernel module and its primary purpose is to
|
||||||
implement WA "drivers" for various peripherals that may or may not be present in a particular setup.
|
implement WA "drivers" for various peripherals that may or may not be present in a particular setup.
|
||||||
|
|
||||||
.. note:: A mudule is itself an Extension and can therefore have it's own modules.
|
.. note:: A mudule is itself an Extension and can therefore have its own modules.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -687,4 +698,3 @@ class Module(Extension):
|
|||||||
|
|
||||||
def initialize(self, context):
|
def initialize(self, context):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@@ -80,8 +80,8 @@ class GlobalParameterAlias(object):
|
|||||||
other_param.kind != param.kind):
|
other_param.kind != param.kind):
|
||||||
message = 'Duplicate global alias {} declared in {} and {} extensions with different types'
|
message = 'Duplicate global alias {} declared in {} and {} extensions with different types'
|
||||||
raise LoaderError(message.format(self.name, ext.name, other_ext.name))
|
raise LoaderError(message.format(self.name, ext.name, other_ext.name))
|
||||||
if not param.name == other_param.name:
|
if param.kind != other_param.kind:
|
||||||
message = 'Two params {} in {} and {} in {} both declare global alias {}'
|
message = 'Two params {} in {} and {} in {} both declare global alias {}, and are of different kinds'
|
||||||
raise LoaderError(message.format(param.name, ext.name,
|
raise LoaderError(message.format(param.name, ext.name,
|
||||||
other_param.name, other_ext.name, self.name))
|
other_param.name, other_ext.name, self.name))
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ class ExtensionLoader(object):
|
|||||||
if should_skip:
|
if should_skip:
|
||||||
continue
|
continue
|
||||||
for fname in files:
|
for fname in files:
|
||||||
if not os.path.splitext(fname)[1].lower() == '.py':
|
if os.path.splitext(fname)[1].lower() != '.py':
|
||||||
continue
|
continue
|
||||||
filepath = os.path.join(root, fname)
|
filepath = os.path.join(root, fname)
|
||||||
try:
|
try:
|
||||||
@@ -401,4 +401,3 @@ def _instantiate(cls, args=None, kwargs=None):
|
|||||||
return cls(*args, **kwargs)
|
return cls(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise LoaderError('Could not load {}'.format(cls), sys.exc_info())
|
raise LoaderError('Could not load {}'.format(cls), sys.exc_info())
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -69,7 +69,11 @@ class ResourceResolver(object):
|
|||||||
self.logger.debug('\t{}'.format(result))
|
self.logger.debug('\t{}'.format(result))
|
||||||
return result
|
return result
|
||||||
if strict:
|
if strict:
|
||||||
raise ResourceError('{} could not be found'.format(resource))
|
if kwargs:
|
||||||
|
criteria = ', '.join(['{}:{}'.format(k, v) for k, v in kwargs.iteritems()])
|
||||||
|
raise ResourceError('{} ({}) could not be found'.format(resource, criteria))
|
||||||
|
else:
|
||||||
|
raise ResourceError('{} could not be found'.format(resource))
|
||||||
self.logger.debug('Resource {} not found.'.format(resource))
|
self.logger.debug('Resource {} not found.'.format(resource))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@@ -82,7 +82,7 @@ class ResourceGetter(Extension):
|
|||||||
Base class for implementing resolvers. Defines resolver interface. Resolvers are
|
Base class for implementing resolvers. Defines resolver interface. Resolvers are
|
||||||
responsible for discovering resources (such as particular kinds of files) they know
|
responsible for discovering resources (such as particular kinds of files) they know
|
||||||
about based on the parameters that are passed to them. Each resolver also has a dict of
|
about based on the parameters that are passed to them. Each resolver also has a dict of
|
||||||
attributes that describe it's operation, and may be used to determine which get invoked.
|
attributes that describe its operation, and may be used to determine which get invoked.
|
||||||
There is no pre-defined set of attributes and resolvers may define their own.
|
There is no pre-defined set of attributes and resolvers may define their own.
|
||||||
|
|
||||||
Class attributes:
|
Class attributes:
|
||||||
|
@@ -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, 4, 0)
|
version = VersionTuple(2, 6, 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.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@@ -71,8 +71,6 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
|||||||
|
|
||||||
* m5 binary. Please make sure that the m5 binary is on the device and
|
* m5 binary. Please make sure that the m5 binary is on the device and
|
||||||
can by found in the path.
|
can by found in the path.
|
||||||
* Busybox. Due to restrictions, we assume that busybox is installed in
|
|
||||||
the guest system, and can be found in the path.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'gem5_android'
|
name = 'gem5_android'
|
||||||
@@ -95,15 +93,27 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def wait_for_boot(self):
|
def wait_for_boot(self):
|
||||||
|
"""
|
||||||
|
Wait for the system to boot
|
||||||
|
|
||||||
|
We monitor the sys.boot_completed and service.bootanim.exit system
|
||||||
|
properties to determine when the system has finished booting. In the
|
||||||
|
event that we cannot coerce the result of service.bootanim.exit to an
|
||||||
|
integer, we assume that the boot animation was disabled and do not wait
|
||||||
|
for it to finish.
|
||||||
|
|
||||||
|
"""
|
||||||
self.logger.info("Waiting for Android to boot...")
|
self.logger.info("Waiting for Android to boot...")
|
||||||
while True:
|
while True:
|
||||||
|
booted = False
|
||||||
|
anim_finished = True # Assume boot animation was disabled on except
|
||||||
try:
|
try:
|
||||||
booted = (1 == int('0' + self.gem5_shell('getprop sys.boot_completed', check_exit_code=False)))
|
booted = (int('0' + self.gem5_shell('getprop sys.boot_completed', check_exit_code=False).strip()) == 1)
|
||||||
anim_finished = (1 == int('0' + self.gem5_shell('getprop service.bootanim.exit', check_exit_code=False)))
|
anim_finished = (int(self.gem5_shell('getprop service.bootanim.exit', check_exit_code=False).strip()) == 1)
|
||||||
if booted and anim_finished:
|
except ValueError:
|
||||||
break
|
|
||||||
except (DeviceError, ValueError):
|
|
||||||
pass
|
pass
|
||||||
|
if booted and anim_finished:
|
||||||
|
break
|
||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
|
|
||||||
self.logger.info("Android booted")
|
self.logger.info("Android booted")
|
||||||
@@ -133,8 +143,8 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
|||||||
self.push_file(filepath, on_device_path)
|
self.push_file(filepath, on_device_path)
|
||||||
# We need to make sure that the folder permissions are set
|
# We need to make sure that the folder permissions are set
|
||||||
# correctly, else the APK does not install correctly.
|
# correctly, else the APK does not install correctly.
|
||||||
self.gem5_shell('busybox chmod 775 /data/local/tmp')
|
self.gem5_shell('chmod 775 /data/local/tmp')
|
||||||
self.gem5_shell('busybox chmod 774 {}'.format(on_device_path))
|
self.gem5_shell('chmod 774 {}'.format(on_device_path))
|
||||||
self.logger.debug("Actually installing the APK: {}".format(on_device_path))
|
self.logger.debug("Actually installing the APK: {}".format(on_device_path))
|
||||||
return self.gem5_shell("pm install {}".format(on_device_path))
|
return self.gem5_shell("pm install {}".format(on_device_path))
|
||||||
else:
|
else:
|
||||||
@@ -146,8 +156,11 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
|||||||
on_device_file = self.path.join(self.working_directory, executable_name)
|
on_device_file = self.path.join(self.working_directory, executable_name)
|
||||||
on_device_executable = self.path.join(self.binaries_directory, executable_name)
|
on_device_executable = self.path.join(self.binaries_directory, executable_name)
|
||||||
self.push_file(filepath, on_device_file)
|
self.push_file(filepath, on_device_file)
|
||||||
self.execute('busybox cp {} {}'.format(on_device_file, on_device_executable))
|
if self.busybox:
|
||||||
self.execute('busybox chmod 0777 {}'.format(on_device_executable))
|
self.execute('{} cp {} {}'.format(self.busybox, on_device_file, on_device_executable))
|
||||||
|
else:
|
||||||
|
self.execute('cat {} > {}'.format(on_device_file, on_device_executable))
|
||||||
|
self.execute('chmod 0777 {}'.format(on_device_executable))
|
||||||
return on_device_executable
|
return on_device_executable
|
||||||
|
|
||||||
def uninstall(self, package):
|
def uninstall(self, package):
|
||||||
@@ -186,16 +199,6 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
|||||||
props = self._get_android_properties(context)
|
props = self._get_android_properties(context)
|
||||||
return props
|
return props
|
||||||
|
|
||||||
def disable_screen_lock(self):
|
|
||||||
"""
|
|
||||||
Attempts to disable he screen lock on the device.
|
|
||||||
|
|
||||||
Overridden here as otherwise we have issues with too many backslashes.
|
|
||||||
"""
|
|
||||||
lockdb = '/data/system/locksettings.db'
|
|
||||||
sqlcommand = "update locksettings set value=\'0\' where name=\'screenlock.disabled\';"
|
|
||||||
self.execute('sqlite3 {} "{}"'.format(lockdb, sqlcommand), as_root=True)
|
|
||||||
|
|
||||||
def capture_screen(self, filepath):
|
def capture_screen(self, filepath):
|
||||||
if BaseGem5Device.capture_screen(self, filepath):
|
if BaseGem5Device.capture_screen(self, filepath):
|
||||||
return
|
return
|
||||||
@@ -203,3 +206,7 @@ class Gem5AndroidDevice(BaseGem5Device, AndroidDevice):
|
|||||||
# If we didn't manage to do the above, call the parent class.
|
# If we didn't manage to do the above, call the parent class.
|
||||||
self.logger.warning("capture_screen: falling back to parent class implementation")
|
self.logger.warning("capture_screen: falling back to parent class implementation")
|
||||||
AndroidDevice.capture_screen(self, filepath)
|
AndroidDevice.capture_screen(self, filepath)
|
||||||
|
|
||||||
|
def initialize(self, context):
|
||||||
|
self.resize_shell()
|
||||||
|
self.deploy_m5(context, force=False)
|
||||||
|
@@ -81,9 +81,7 @@ class Juno(BigLittleDevice):
|
|||||||
'fdt_support': True,
|
'fdt_support': True,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Parameter('bootargs', default='console=ttyAMA0,115200 earlyprintk=pl011,0x7ff80000 '
|
Parameter('bootargs',
|
||||||
'verbose debug init=/init root=/dev/sda1 rw ip=dhcp '
|
|
||||||
'rootwait video=DVI-D-1:1920x1080R@60',
|
|
||||||
description='''Default boot arguments to use when boot_arguments were not.'''),
|
description='''Default boot arguments to use when boot_arguments were not.'''),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -106,7 +104,7 @@ class Juno(BigLittleDevice):
|
|||||||
if self.bootloader == 'uefi':
|
if self.bootloader == 'uefi':
|
||||||
self._boot_via_uefi()
|
self._boot_via_uefi()
|
||||||
else:
|
else:
|
||||||
self._boot_via_uboot(**self.bootargs)
|
self._boot_via_uboot(bootargs=self.bootargs)
|
||||||
|
|
||||||
def _boot_via_uboot(self, **kwargs):
|
def _boot_via_uboot(self, **kwargs):
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
@@ -158,7 +156,7 @@ class Juno(BigLittleDevice):
|
|||||||
target.sendline('ip addr list eth0')
|
target.sendline('ip addr list eth0')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
try:
|
try:
|
||||||
target.expect('inet ([1-9]\d*.\d+.\d+.\d+)', timeout=10)
|
target.expect(r'inet ([1-9]\d*.\d+.\d+.\d+)', timeout=10)
|
||||||
self.adb_name = target.match.group(1) + ':5555' # pylint: disable=W0201
|
self.adb_name = target.match.group(1) + ':5555' # pylint: disable=W0201
|
||||||
break
|
break
|
||||||
except pexpect.TIMEOUT:
|
except pexpect.TIMEOUT:
|
||||||
@@ -220,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'
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -19,6 +19,7 @@ import logging
|
|||||||
|
|
||||||
from wlauto import LinuxDevice, Parameter
|
from wlauto import LinuxDevice, Parameter
|
||||||
from wlauto.common.gem5.device import BaseGem5Device
|
from wlauto.common.gem5.device import BaseGem5Device
|
||||||
|
from wlauto.utils import types
|
||||||
|
|
||||||
|
|
||||||
class Gem5LinuxDevice(BaseGem5Device, LinuxDevice):
|
class Gem5LinuxDevice(BaseGem5Device, LinuxDevice):
|
||||||
@@ -68,8 +69,6 @@ class Gem5LinuxDevice(BaseGem5Device, LinuxDevice):
|
|||||||
|
|
||||||
* m5 binary. Please make sure that the m5 binary is on the device and
|
* m5 binary. Please make sure that the m5 binary is on the device and
|
||||||
can by found in the path.
|
can by found in the path.
|
||||||
* Busybox. Due to restrictions, we assume that busybox is installed in
|
|
||||||
the guest system, and can be found in the path.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'gem5_linux'
|
name = 'gem5_linux'
|
||||||
@@ -80,6 +79,11 @@ class Gem5LinuxDevice(BaseGem5Device, LinuxDevice):
|
|||||||
Parameter('core_clusters', default=[], override=True),
|
Parameter('core_clusters', default=[], override=True),
|
||||||
Parameter('host', default='localhost', override=True,
|
Parameter('host', default='localhost', override=True,
|
||||||
description='Host name or IP address for the device.'),
|
description='Host name or IP address for the device.'),
|
||||||
|
Parameter('login_prompt', kind=types.list_of_strs,
|
||||||
|
default=['login:', 'AEL login:', 'username:'],
|
||||||
|
mandatory=False),
|
||||||
|
Parameter('login_password_prompt', kind=types.list_of_strs,
|
||||||
|
default=['password:'], mandatory=False),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Overwritten from Device. For documentation, see corresponding method in
|
# Overwritten from Device. For documentation, see corresponding method in
|
||||||
@@ -92,14 +96,14 @@ class Gem5LinuxDevice(BaseGem5Device, LinuxDevice):
|
|||||||
|
|
||||||
def login_to_device(self):
|
def login_to_device(self):
|
||||||
# Wait for the login prompt
|
# Wait for the login prompt
|
||||||
i = self.sckt.expect([r'login:', r'username:', self.sckt.UNIQUE_PROMPT],
|
prompt = self.login_prompt + [self.sckt.UNIQUE_PROMPT]
|
||||||
timeout=10)
|
i = self.sckt.expect(prompt, timeout=10)
|
||||||
# Check if we are already at a prompt, or if we need to log in.
|
# Check if we are already at a prompt, or if we need to log in.
|
||||||
if i < 2:
|
if i < len(prompt) - 1:
|
||||||
self.sckt.sendline("{}".format(self.username))
|
self.sckt.sendline("{}".format(self.username))
|
||||||
j = self.sckt.expect([r'password:', r'# ', self.sckt.UNIQUE_PROMPT],
|
password_prompt = self.login_password_prompt + [r'# ', self.sckt.UNIQUE_PROMPT]
|
||||||
timeout=self.delay)
|
j = self.sckt.expect(password_prompt, timeout=self.delay)
|
||||||
if j == 2:
|
if j < len(password_prompt) - 2:
|
||||||
self.sckt.sendline("{}".format(self.password))
|
self.sckt.sendline("{}".format(self.password))
|
||||||
self.sckt.expect([r'# ', self.sckt.UNIQUE_PROMPT], timeout=self.delay)
|
self.sckt.expect([r'# ', self.sckt.UNIQUE_PROMPT], timeout=self.delay)
|
||||||
|
|
||||||
@@ -110,3 +114,7 @@ class Gem5LinuxDevice(BaseGem5Device, LinuxDevice):
|
|||||||
# If we didn't manage to do the above, call the parent class.
|
# If we didn't manage to do the above, call the parent class.
|
||||||
self.logger.warning("capture_screen: falling back to parent class implementation")
|
self.logger.warning("capture_screen: falling back to parent class implementation")
|
||||||
LinuxDevice.capture_screen(self, filepath)
|
LinuxDevice.capture_screen(self, filepath)
|
||||||
|
|
||||||
|
def initialize(self, context):
|
||||||
|
self.resize_shell()
|
||||||
|
self.deploy_m5(context, force=False)
|
||||||
|
@@ -29,7 +29,6 @@ class GenericDevice(LinuxDevice):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
abi = 'armeabi'
|
|
||||||
has_gpu = True
|
has_gpu = True
|
||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
|
@@ -32,4 +32,3 @@ class OdroidXU3LinuxDevice(LinuxDevice):
|
|||||||
]
|
]
|
||||||
|
|
||||||
abi = 'armeabi'
|
abi = 'armeabi'
|
||||||
|
|
||||||
|
19
wlauto/external/HelloJni/HelloJNI.iml
vendored
Normal file
19
wlauto/external/HelloJni/HelloJNI.iml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module external.linked.project.id="HelloJNI" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="java-gradle" name="Java-Gradle">
|
||||||
|
<configuration>
|
||||||
|
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||||
|
<option name="BUILDABLE" value="false" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
53
wlauto/external/HelloJni/README.md
vendored
Normal file
53
wlauto/external/HelloJni/README.md
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
Hello JNI
|
||||||
|
=========
|
||||||
|
Hello JNI is an Android sample that uses JNI to call C code from a Android Java Activity.
|
||||||
|
|
||||||
|
This sample uses the new [Android Studio CMake plugin](http://tools.android.com/tech-docs/external-c-builds) with C++ support.
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
--------------
|
||||||
|
- Android Studio 2.2+ with [NDK](https://developer.android.com/ndk/) bundle.
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
1. [Download Android Studio](http://developer.android.com/sdk/index.html)
|
||||||
|
1. Launch Android Studio.
|
||||||
|
1. Open the sample directory.
|
||||||
|
1. Open *File/Project Structure...*
|
||||||
|
- Click *Download* or *Select NDK location*.
|
||||||
|
1. Click *Tools/Android/Sync Project with Gradle Files*.
|
||||||
|
1. Click *Run/Run 'app'*.
|
||||||
|
|
||||||
|
Screenshots
|
||||||
|
-----------
|
||||||
|

|
||||||
|
|
||||||
|
Support
|
||||||
|
-------
|
||||||
|
If you've found an error in these samples, please [file an issue](https://github.com/googlesamples/android-ndk/issues/new).
|
||||||
|
|
||||||
|
Patches are encouraged, and may be submitted by [forking this project](https://github.com/googlesamples/android-ndk/fork) and
|
||||||
|
submitting a pull request through GitHub. Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
|
||||||
|
|
||||||
|
- [Stack Overflow](http://stackoverflow.com/questions/tagged/android-ndk)
|
||||||
|
- [Google+ Community](https://plus.google.com/communities/105153134372062985968)
|
||||||
|
- [Android Tools Feedbacks](http://tools.android.com/feedback)
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Copyright 2015 Google, Inc.
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||||
|
license agreements. See the NOTICE file distributed with this work for
|
||||||
|
additional information regarding copyright ownership. The ASF licenses this
|
||||||
|
file to you 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.
|
6
wlauto/external/HelloJni/app/.directory
vendored
Normal file
6
wlauto/external/HelloJni/app/.directory
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[Dolphin]
|
||||||
|
Timestamp=2017,1,31,16,18,16
|
||||||
|
Version=3
|
||||||
|
|
||||||
|
[Settings]
|
||||||
|
HiddenFilesShown=true
|
BIN
wlauto/external/HelloJni/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a/.ninja_deps
vendored
Normal file
BIN
wlauto/external/HelloJni/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a/.ninja_deps
vendored
Normal file
Binary file not shown.
5
wlauto/external/HelloJni/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a/.ninja_log
vendored
Normal file
5
wlauto/external/HelloJni/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a/.ninja_log
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# ninja log v5
|
||||||
|
0 24 0 CMakeFiles/hello-jni.dir/hello-jni.c.o ca1c114d175525bf
|
||||||
|
24 53 0 /data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/build/intermediates/cmake/arm7/debug/obj/armeabi-v7a/libhello-jni.so 4734b0c6af87f1ca
|
||||||
|
0 1180 0 CMakeFiles/hello-jni.dir/hello-jni.c.o ca1c114d175525bf
|
||||||
|
1180 1471 0 /data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/build/intermediates/cmake/arm7/debug/obj/armeabi-v7a/libhello-jni.so 4734b0c6af87f1ca
|
316
wlauto/external/HelloJni/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a/CMakeCache.txt
vendored
Normal file
316
wlauto/external/HelloJni/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a/CMakeCache.txt
vendored
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
# This is the CMakeCache file.
|
||||||
|
# For build in directory: /data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a
|
||||||
|
# It was generated by CMake: /data/marc/Software/android-sdk-linux/cmake/3.6.3155560/bin/cmake
|
||||||
|
# You can edit this file to change values found and used by cmake.
|
||||||
|
# If you do not want to change any of the values, simply exit the editor.
|
||||||
|
# If you do want to change a value, simply edit, save, and exit the editor.
|
||||||
|
# The syntax for the file is as follows:
|
||||||
|
# KEY:TYPE=VALUE
|
||||||
|
# KEY is the name of a variable in the cache.
|
||||||
|
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
|
||||||
|
# VALUE is the current value for the KEY.
|
||||||
|
|
||||||
|
########################
|
||||||
|
# EXTERNAL cache entries
|
||||||
|
########################
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
ANDROID_ABI:UNINITIALIZED=armeabi-v7a
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
ANDROID_NATIVE_API_LEVEL:UNINITIALIZED=19
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
ANDROID_NDK:UNINITIALIZED=/data/marc/Software/android-sdk-linux/ndk-bundle
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
ANDROID_TOOLCHAIN:UNINITIALIZED=clang
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_AR:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar
|
||||||
|
|
||||||
|
//Choose the type of build, options are: None(CMAKE_CXX_FLAGS or
|
||||||
|
// CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.
|
||||||
|
CMAKE_BUILD_TYPE:STRING=Debug
|
||||||
|
|
||||||
|
//Flags used by the compiler during all build types.
|
||||||
|
CMAKE_CXX_FLAGS:STRING=
|
||||||
|
|
||||||
|
//Flags used by the compiler during debug builds.
|
||||||
|
CMAKE_CXX_FLAGS_DEBUG:STRING=
|
||||||
|
|
||||||
|
//Flags used by the compiler during release builds for minimum
|
||||||
|
// size.
|
||||||
|
CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
|
||||||
|
|
||||||
|
//Flags used by the compiler during release builds.
|
||||||
|
CMAKE_CXX_FLAGS_RELEASE:STRING=
|
||||||
|
|
||||||
|
//Flags used by the compiler during release builds with debug info.
|
||||||
|
CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
|
||||||
|
|
||||||
|
//Libraries linked by default with all C++ applications.
|
||||||
|
CMAKE_CXX_STANDARD_LIBRARIES:STRING=-lm "/data/marc/Software/android-sdk-linux/ndk-bundle/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/libgnustl_static.a"
|
||||||
|
|
||||||
|
//Flags used by the compiler during all build types.
|
||||||
|
CMAKE_C_FLAGS:STRING=
|
||||||
|
|
||||||
|
//Flags used by the compiler during debug builds.
|
||||||
|
CMAKE_C_FLAGS_DEBUG:STRING=
|
||||||
|
|
||||||
|
//Flags used by the compiler during release builds for minimum
|
||||||
|
// size.
|
||||||
|
CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
|
||||||
|
|
||||||
|
//Flags used by the compiler during release builds.
|
||||||
|
CMAKE_C_FLAGS_RELEASE:STRING=
|
||||||
|
|
||||||
|
//Flags used by the compiler during release builds with debug info.
|
||||||
|
CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
|
||||||
|
|
||||||
|
//Libraries linked by default with all C applications.
|
||||||
|
CMAKE_C_STANDARD_LIBRARIES:STRING=-lm
|
||||||
|
|
||||||
|
//Flags used by the linker.
|
||||||
|
CMAKE_EXE_LINKER_FLAGS:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during debug builds.
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release minsize builds.
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release builds.
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during Release with Debug Info builds.
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||||
|
|
||||||
|
//Enable/Disable output of compile commands during generation.
|
||||||
|
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=OFF
|
||||||
|
|
||||||
|
//Install path prefix, prepended onto install directories.
|
||||||
|
CMAKE_INSTALL_PREFIX:PATH=/usr/local
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
CMAKE_LIBRARY_OUTPUT_DIRECTORY:UNINITIALIZED=/data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/build/intermediates/cmake/arm7/debug/obj/armeabi-v7a
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_LINKER:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ld
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
CMAKE_MAKE_PROGRAM:UNINITIALIZED=/data/marc/Software/android-sdk-linux/cmake/3.6.3155560/bin/ninja
|
||||||
|
|
||||||
|
//Flags used by the linker during the creation of modules.
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during debug builds.
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release minsize builds.
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release builds.
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during Release with Debug Info builds.
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_NM:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-nm
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_OBJCOPY:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objcopy
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_OBJDUMP:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
CMAKE_PROJECT_NAME:STATIC=Project
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_RANLIB:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ranlib
|
||||||
|
|
||||||
|
//Flags used by the linker during the creation of dll's.
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during debug builds.
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release minsize builds.
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release builds.
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during Release with Debug Info builds.
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||||
|
|
||||||
|
//If set, runtime paths are not added when installing shared libraries,
|
||||||
|
// but are added when building.
|
||||||
|
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
|
||||||
|
|
||||||
|
//If set, runtime paths are not added when using shared libraries.
|
||||||
|
CMAKE_SKIP_RPATH:BOOL=NO
|
||||||
|
|
||||||
|
//Flags used by the linker during the creation of static libraries.
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during debug builds.
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release minsize builds.
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during release builds.
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
|
||||||
|
|
||||||
|
//Flags used by the linker during Release with Debug Info builds.
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_STRIP:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip
|
||||||
|
|
||||||
|
//The CMake toolchain file
|
||||||
|
CMAKE_TOOLCHAIN_FILE:FILEPATH=/data/marc/Software/android-sdk-linux/ndk-bundle/build/cmake/android.toolchain.cmake
|
||||||
|
|
||||||
|
//If this value is on, makefiles will be generated without the
|
||||||
|
// .SILENT directive, and all commands will be echoed to the console
|
||||||
|
// during the make. This is useful for debugging only. With Visual
|
||||||
|
// Studio IDE projects all commands are done without /nologo.
|
||||||
|
CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
Project_BINARY_DIR:STATIC=/data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
Project_SOURCE_DIR:STATIC=/data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/src/main/cpp
|
||||||
|
|
||||||
|
//Dependencies for the target
|
||||||
|
hello-jni_LIB_DEPENDS:STATIC=general;android;general;log;
|
||||||
|
|
||||||
|
|
||||||
|
########################
|
||||||
|
# INTERNAL cache entries
|
||||||
|
########################
|
||||||
|
|
||||||
|
//ADVANCED property for variable: CMAKE_AR
|
||||||
|
CMAKE_AR-ADVANCED:INTERNAL=1
|
||||||
|
//This is the directory where this CMakeCache.txt was created
|
||||||
|
CMAKE_CACHEFILE_DIR:INTERNAL=/data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/.externalNativeBuild/cmake/arm7Debug/armeabi-v7a
|
||||||
|
//Major version of cmake used to create the current loaded cache
|
||||||
|
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
|
||||||
|
//Minor version of cmake used to create the current loaded cache
|
||||||
|
CMAKE_CACHE_MINOR_VERSION:INTERNAL=6
|
||||||
|
//Patch version of cmake used to create the current loaded cache
|
||||||
|
CMAKE_CACHE_PATCH_VERSION:INTERNAL=0
|
||||||
|
//Path to CMake executable.
|
||||||
|
CMAKE_COMMAND:INTERNAL=/data/marc/Software/android-sdk-linux/cmake/3.6.3155560/bin/cmake
|
||||||
|
//Path to cpack program executable.
|
||||||
|
CMAKE_CPACK_COMMAND:INTERNAL=/data/marc/Software/android-sdk-linux/cmake/3.6.3155560/bin/cpack
|
||||||
|
//Path to ctest program executable.
|
||||||
|
CMAKE_CTEST_COMMAND:INTERNAL=/data/marc/Software/android-sdk-linux/cmake/3.6.3155560/bin/ctest
|
||||||
|
//ADVANCED property for variable: CMAKE_CXX_FLAGS
|
||||||
|
CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG
|
||||||
|
CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL
|
||||||
|
CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE
|
||||||
|
CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||||
|
CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_CXX_STANDARD_LIBRARIES
|
||||||
|
CMAKE_CXX_STANDARD_LIBRARIES-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_C_FLAGS
|
||||||
|
CMAKE_C_FLAGS-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG
|
||||||
|
CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL
|
||||||
|
CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE
|
||||||
|
CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO
|
||||||
|
CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_C_STANDARD_LIBRARIES
|
||||||
|
CMAKE_C_STANDARD_LIBRARIES-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
|
||||||
|
CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
|
||||||
|
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS
|
||||||
|
CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1
|
||||||
|
//Name of external makefile project generator.
|
||||||
|
CMAKE_EXTRA_GENERATOR:INTERNAL=Android Gradle
|
||||||
|
//Name of generator.
|
||||||
|
CMAKE_GENERATOR:INTERNAL=Ninja
|
||||||
|
//Name of generator platform.
|
||||||
|
CMAKE_GENERATOR_PLATFORM:INTERNAL=
|
||||||
|
//Name of generator toolset.
|
||||||
|
CMAKE_GENERATOR_TOOLSET:INTERNAL=
|
||||||
|
//Source directory with the top level CMakeLists.txt file for this
|
||||||
|
// project
|
||||||
|
CMAKE_HOME_DIRECTORY:INTERNAL=/data/marc/Work/my_wa_tests/revent/vsync/HelloJNI/app/src/main/cpp
|
||||||
|
//Install .so files without execute permission.
|
||||||
|
CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_LINKER
|
||||||
|
CMAKE_LINKER-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
|
||||||
|
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_NM
|
||||||
|
CMAKE_NM-ADVANCED:INTERNAL=1
|
||||||
|
//number of local generators
|
||||||
|
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_OBJCOPY
|
||||||
|
CMAKE_OBJCOPY-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_OBJDUMP
|
||||||
|
CMAKE_OBJDUMP-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_RANLIB
|
||||||
|
CMAKE_RANLIB-ADVANCED:INTERNAL=1
|
||||||
|
//Path to CMake installation.
|
||||||
|
CMAKE_ROOT:INTERNAL=/data/marc/Software/android-sdk-linux/cmake/3.6.3155560/share/cmake-3.6
|
||||||
|
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
|
||||||
|
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
|
||||||
|
CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_SKIP_RPATH
|
||||||
|
CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
|
||||||
|
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||||
|
//ADVANCED property for variable: CMAKE_STRIP
|
||||||
|
CMAKE_STRIP-ADVANCED:INTERNAL=1
|
||||||
|
//uname command
|
||||||
|
CMAKE_UNAME:INTERNAL=/bin/uname
|
||||||
|
//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
|
||||||
|
CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user