1#!/usr/bin/env python3
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
16"""
17Script for testing various download stress scenarios.
18
19"""
20import os
21import threading
22import uuid
23
24from acts.base_test import BaseTestClass
25from acts import signals
26from acts.controllers.ap_lib import hostapd_constants
27from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate
28from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
29from acts.test_utils.fuchsia import utils
30from acts.test_utils.tel.tel_test_utils import setup_droid_properties
31from acts.utils import rand_ascii_str
32
33
34class DownloadStressTest(BaseTestClass):
35    # Default number of test iterations here.
36    # Override using parameter in config file.
37    # Eg: "download_stress_test_iterations": "10"
38    num_of_iterations = 3
39
40    # Timeout for download thread in seconds
41    download_timeout_s = 60 * 5
42
43    # Download urls
44    url_20MB = 'http://ipv4.download.thinkbroadband.com/20MB.zip'
45    url_40MB = 'http://ipv4.download.thinkbroadband.com/40MB.zip'
46    url_60MB = 'http://ipv4.download.thinkbroadband.com/60MB.zip'
47    url_512MB = 'http://ipv4.download.thinkbroadband.com/512MB.zip'
48
49    # Constants used in test_one_large_multiple_small_downloads
50    download_small_url = url_20MB
51    download_large_url = url_512MB
52    num_of_small_downloads = 5
53    download_threads_result = []
54
55    def setup_class(self):
56        super().setup_class()
57        self.ssid = rand_ascii_str(10)
58        self.fd = self.fuchsia_devices[0]
59        self.wlan_device = create_wlan_device(self.fd)
60        self.ap = self.access_points[0]
61        self.num_of_iterations = int(
62            self.user_params.get("download_stress_test_iterations",
63                                 self.num_of_iterations))
64
65        setup_ap_and_associate(
66            access_point=self.ap,
67            client=self.wlan_device,
68            profile_name='whirlwind',
69            channel=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
70            ssid=self.ssid)
71
72    def teardown_test(self):
73        self.download_threads_result.clear()
74        self.wlan_device.disconnect()
75        self.wlan_device.reset_wifi()
76        self.ap.stop_all_aps()
77
78    def test_download_small(self):
79        self.log.info("Downloading small file")
80        return self.download_file(self.url_20MB)
81
82    def test_download_large(self):
83        return self.download_file(self.url_512MB)
84
85    def test_continuous_download(self):
86        for x in range(0, self.num_of_iterations):
87            if not self.download_file(self.url_512MB):
88                return False
89        return True
90
91    def download_file(self, url):
92        self.log.info("Start downloading: %s" % url)
93        return utils.http_file_download_by_curl(
94            self.fd,
95            url,
96            additional_args='--max-time %d --silent' % self.download_timeout_s)
97
98    def download_thread(self, url):
99        download_status = self.download_file(url)
100        if download_status:
101            self.log.info("Success downloading: %s" % url)
102        else:
103            self.log.info("Failure downloading: %s" % url)
104
105        self.download_threads_result.append(download_status)
106        return download_status
107
108    def test_multi_downloads(self):
109        download_urls = [self.url_20MB, self.url_40MB, self.url_60MB]
110        download_threads = []
111
112        try:
113            # Start multiple downloads at the same time
114            for index, url in enumerate(download_urls):
115                self.log.info('Create and start thread %d.' % index)
116                t = threading.Thread(target=self.download_thread, args=(url, ))
117                download_threads.append(t)
118                t.start()
119
120            # Wait for all threads to complete or timeout
121            for t in download_threads:
122                t.join(self.download_timeout_s)
123
124        finally:
125            is_alive = False
126
127            for index, t in enumerate(download_threads):
128                if t.isAlive():
129                    t = None
130                    is_alive = True
131
132            if is_alive:
133                raise signals.TestFailure('Thread %d timedout' % index)
134
135        for index in range(0, len(self.download_threads_result)):
136            if not self.download_threads_result[index]:
137                self.log.info("Download failed for %d" % index)
138                raise signals.TestFailure(
139                    'Thread %d failed to download' % index)
140                return False
141
142        return True
143
144    def test_one_large_multiple_small_downloads(self):
145        for index in range(self.num_of_iterations):
146            download_threads = []
147            try:
148                large_thread = threading.Thread(
149                    target=self.download_thread,
150                    args=(self.download_large_url, ))
151                download_threads.append(large_thread)
152                large_thread.start()
153
154                for i in range(self.num_of_small_downloads):
155                    # Start small file download
156                    t = threading.Thread(
157                        target=self.download_thread,
158                        args=(self.download_small_url, ))
159                    download_threads.append(t)
160                    t.start()
161                    # Wait for thread to exit before starting the next iteration
162                    t.join(self.download_timeout_s)
163
164                # Wait for the large file download thread to complete
165                large_thread.join(self.download_timeout_s)
166
167            finally:
168                is_alive = False
169
170                for index, t in enumerate(download_threads):
171                    if t.isAlive():
172                        t = None
173                        is_alive = True
174
175                if is_alive:
176                    raise signals.TestFailure('Thread %d timedout' % index)
177
178            for index in range(0, len(self.download_threads_result)):
179                if not self.download_threads_result[index]:
180                    self.log.info("Download failed for %d" % index)
181                    raise signals.TestFailure(
182                        'Thread %d failed to download' % index)
183                    return False
184
185            # Clear results before looping again
186            self.download_threads_result.clear()
187
188        return True
189