def test_instrumenter_fails_gracefully(self, instrumenter_cls): # Configure the instrumenter class to return a mock instr_mock = mock.MagicMock(SrcInstrumenter) instrumenter_cls.return_value = instr_mock # Configure the mock to raise an exception instr_mock.instrumented_src.side_effect = SrcInstrumenterError # Create a mock description with one source file mock_desc = self._mock_suite_desc('test-suite-0', os.getcwd(), ['src.js']) # Create the uninstrumented version of the source file expected_page = 'uninstrumented source' with open('src.js', 'w') as src_file: src_file.write(expected_page) # Create a suite page server for those descriptions server = SuitePageServer( [mock_desc], mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH ) # Start the server server.start() self.addCleanup(server.stop) # Even though the instrumenter failed, # we should STILL be able to get the uninstrumented # version of the source file url = server.root_url() + "suite/test-suite-0/include/src.js" response = requests.get(url, timeout=0.1) self.assertEqual(response.text, expected_page)
def test_collects_POST_coverage_info(self): # Start the page server server = SuitePageServer([self._mock_suite_desc('test-suite-0', '/root', ['src.js'])], mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH) server.start() self.addCleanup(server.stop) # POST some coverage data to the src page # This test does NOT mock the CoverageData class created internally, # so we need to pass valid JSON data. # (Since CoverageData involves no network or file access, mocking # it is not worth the effort). coverage_data = {'/src.js': {'lineData': [1, 0, None, 2, 1, None, 0]}} requests.post(server.root_url() + "jscoverage-store/test-suite-0", data=json.dumps(coverage_data), timeout=0.1) # Get the results immediately from the server. # It's the server's responsibility to block until all results are received. result_data = server.all_coverage_data() # Check the result self.assertEqual(result_data.src_list(), ['/root/src.js']) self.assertEqual(result_data.line_dict_for_src('/root/src.js'), {0: True, 1: False, 3: True, 4: True, 6: False})
def test_does_not_instrument_lib_or_spec_files(self, instrumenter_cls): # Configure the instrumenter class to return a mock instr_mock = mock.MagicMock(SrcInstrumenter) instrumenter_cls.return_value = instr_mock # Create a mock description with lib and spec files mock_desc = self._mock_suite_desc( 'test-suite-0', '/root', ['src.js'], lib_paths=['lib.js'], spec_paths=['spec.js'] ) # Create a suite page server for the description server = SuitePageServer([mock_desc], mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH) # Start the server server.start() self.addCleanup(server.stop) # Access the lib and spec pages url_list = [server.root_url() + "suite/test-suite-0/include/lib.js", server.root_url() + "suite/test-suite-0/include/spec.js"] for url in url_list: requests.get(url, timeout=0.1) # Ensure that the instrumenter was NOT invoked, # since these are not source files self.assertFalse(instr_mock.instrumented_src.called)
def test_serves_instrumented_source_files(self, instrumenter_cls): # Configure the instrumenter class to return a mock instr_mock = mock.MagicMock(SrcInstrumenter) instrumenter_cls.return_value = instr_mock # Configure the instrumenter to always return fake output fake_src = u"instr\u1205ented sr\u1239 output" instr_mock.instrumented_src.return_value = fake_src # Create a mock description with one source file mock_desc = self._mock_suite_desc('test-suite-0', '/root', ['src.js']) # Create a suite page server for those descriptions server = SuitePageServer([mock_desc], mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH) # Start the server server.start() self.addCleanup(server.stop) # Access the page, expecting to get the instrumented source url = server.root_url() + "suite/test-suite-0/include/src.js" response = requests.get(url, timeout=0.1) self.assertEqual(response.text, fake_src)
def test_creates_instrumenters_for_suites(self, instrumenter_cls): # Configure the instrumenter class to return mocks instr_mocks = [mock.MagicMock(SrcInstrumenter), mock.MagicMock(SrcInstrumenter)] instrumenter_cls.side_effect = instr_mocks # Set up the descriptions mock_desc_list = [self._mock_suite_desc('test-suite-0', '/root_1', ['src1.js', 'src2.js']), self._mock_suite_desc('test-suite-1', '/root_2', ['src3.js', 'src4.js'])] # Create a suite page server for those descriptions server = SuitePageServer(mock_desc_list, mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH) # Start the server server.start() self.addCleanup(server.stop) # Expect that there is a SrcInstrumenter for each suite, # and it has been started. instr_dict = server.src_instr_dict self.assertEqual(len(instr_dict), len(mock_desc_list)) for instr in instr_dict.values(): instr.start.assert_called_once_with() # Stop the server # Expect that all the instrumenters are also stopped server.stop() for instr in instr_mocks: instr.stop.assert_called_once_with()
def setUp(self): # Call the superclass implementation to create the temp workspace super(SuitePageServerTest, self).setUp() # Create mock suite descriptions self.suite_desc_list = [ mock.MagicMock(SuiteDescription) for _ in range(self.NUM_SUITE_DESC) ] # Configure the mock suite descriptions to have no dependencies suite_num = 0 for suite in self.suite_desc_list: suite.suite_name.return_value = 'test-suite-{}'.format(suite_num) suite.lib_paths.return_value = [] suite.src_paths.return_value = [] suite.spec_paths.return_value = [] suite.fixture_paths.return_value = [] suite.root_dir.return_value = os.getcwd() suite_num += 1 # Create a mock suite renderer self.suite_renderer = mock.MagicMock(SuiteRenderer) # Create the server self.server = SuitePageServer( self.suite_desc_list, self.suite_renderer ) # Start the server self.server.start()
def test_uncovered_src(self): # Create the source file -- we need to do this # CoverageData can determine the number of uncovered # lines (every line in the file) num_lines = 5 with open('src.js', 'w') as src_file: contents = '\n'.join(['test line' for _ in range(num_lines)]) src_file.write(contents) # Start the page server root_dir = self.temp_dir server = SuitePageServer([self._mock_suite_desc('test-suite-0', root_dir, ['src.js'])], mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH) server.start() self.addCleanup(server.stop) # POST empty coverage data back to the server # Since no coverage information is reported, we expect # that the source file in the suite description is # reported as uncovered. coverage_data = {} requests.post(server.root_url() + "jscoverage-store/test-suite-0", data=json.dumps(coverage_data), timeout=0.1) # Get the results immediately from the server. # It's the server's responsibility to block until all results are received. result_data = server.all_coverage_data() # Check the result -- expect that the source file # is reported as completely uncovered full_src_path = os.path.join(root_dir, 'src.js') self.assertEqual(result_data.src_list(), [full_src_path]) self.assertEqual(result_data.line_dict_for_src(full_src_path), {line_num: False for line_num in range(num_lines)})
def test_timeout_if_missing_coverage(self): # Start the page server with multiple descriptions mock_desc_list = [self._mock_suite_desc('test-suite-0', '/root_1', ['src1.js', 'src2.js']), self._mock_suite_desc('test-suite-1', '/root_2', ['src.js'])] server = SuitePageServer(mock_desc_list, mock.MagicMock(SuiteRenderer), jscover_path=self.JSCOVER_PATH) server.start() self.addCleanup(server.stop) # POST coverage data to one of the sources, but not the other coverage_data = {'/suite/test-suite-0/include/src1.js': {'lineData': [1]}} requests.post(server.root_url() + "jscoverage-store/test-suite-0", data=json.dumps(coverage_data), timeout=0.1) # Try to get the coverage data; expect it to timeout # We configured the timeout to be short in our setup method # so this should return quickly. with self.assertRaises(TimeoutError): server.all_coverage_data()
class SuitePageServerTest(TempWorkspaceTestCase): NUM_SUITE_DESC = 2 def setUp(self): # Call the superclass implementation to create the temp workspace super(SuitePageServerTest, self).setUp() # Create mock suite descriptions self.suite_desc_list = [ mock.MagicMock(SuiteDescription) for _ in range(self.NUM_SUITE_DESC) ] # Configure the mock suite descriptions to have no dependencies suite_num = 0 for suite in self.suite_desc_list: suite.suite_name.return_value = 'test-suite-{}'.format(suite_num) suite.lib_paths.return_value = [] suite.src_paths.return_value = [] suite.spec_paths.return_value = [] suite.fixture_paths.return_value = [] suite.root_dir.return_value = os.getcwd() suite_num += 1 # Create a mock suite renderer self.suite_renderer = mock.MagicMock(SuiteRenderer) # Create the server self.server = SuitePageServer( self.suite_desc_list, self.suite_renderer ) # Start the server self.server.start() def tearDown(self): # Stop the server, which frees the port self.server.stop() def test_root_url(self): # Check that the root URL has the right form url_regex = re.compile('^http://0.0.0.0:[0-9]+/$') url = self.server.root_url() result = url_regex.match(url) self.assertIsNot(result, None, msg="URL has incorrect format: '{}'".format(url)) def test_suite_url_list(self): # Retrieve the urls for each test suite page url_list = self.server.suite_url_list() # Expect that we have the correct number of URLs self.assertEqual(len(url_list), self.NUM_SUITE_DESC) # Expect that the URLs have the correct form for suite_num in range(self.NUM_SUITE_DESC): expected_url = self.server.root_url() + u'suite/test-suite-{}'.format(suite_num) self.assertIn(expected_url, url_list) def test_enforce_unique_suite_names(self): # Try to create a suite server in which two suites have the same name suite_desc_list = [ mock.MagicMock(SuiteDescription) for _ in range(4) ] suite_desc_list[0].suite_name.return_value = 'test-suite-1' suite_desc_list[1].suite_name.return_value = 'test-suite-2' suite_desc_list[2].suite_name.return_value = 'test-suite-1' suite_desc_list[3].suite_name.return_value = 'test-suite-3' # Expect an error when initializing the server with self.assertRaises(DuplicateSuiteNameError): SuitePageServer(suite_desc_list, self.suite_renderer) def test_serve_suite_pages(self): # Configure the suite renderer to return a test string expected_page = u'test suite mock' self.suite_renderer.render_to_string.return_value = expected_page # Check that we can load each page in the suite for url in self.server.suite_url_list(): self._assert_page_equals(url, expected_page) def test_serve_suite_pages_ignore_get_params(self): # Configure the suite renderer to return a test string expected_page = u'test suite mock' self.suite_renderer.render_to_string.return_value = expected_page # Check that we can load each page in the suite, # even if we add additional GET params for url in self.server.suite_url_list(): url = url + "?param=12345" self._assert_page_equals(url, expected_page) def test_serve_runners(self): for path in ['jasmine/jasmine.css', 'jasmine/jasmine.js', 'jasmine/jasmine-json.js', 'jasmine/jasmine-html.js']: pkg_path = 'runner/' + path expected_page = pkg_resources.resource_string('js_test_tool', pkg_path) url = self.server.root_url() + pkg_path self._assert_page_equals(url, expected_page) def test_ignore_runner_get_params(self): for path in ['jasmine/jasmine.css', 'jasmine/jasmine.js', 'jasmine/jasmine-json.js', 'jasmine/jasmine-html.js']: pkg_path = 'runner/' + path expected_page = pkg_resources.resource_string('js_test_tool', pkg_path) # Append GET params to the URL url = self.server.root_url() + pkg_path + "?param=abc.123&another=87" # Should still be able to load the page self._assert_page_equals(url, expected_page) def test_serve_lib_js(self): # Configure the suite description to contain JS dependencies lib_paths = ['lib/1.js', 'lib/subdir/2.js'] self.suite_desc_list[0].lib_paths.return_value = lib_paths # Create fake files to serve os.makedirs('lib/subdir') expected_page = u'\u0236est \u023Dib file' self._create_fake_files(lib_paths, expected_page) # Expect that the server sends us the files for path in lib_paths: url = self.server.root_url() + 'suite/test-suite-0/include/' + path self._assert_page_equals(url, expected_page) def test_serve_src_js(self): # Configure the suite description to contain JS source files src_paths = ['src/1.js', 'src/subdir/2.js'] self.suite_desc_list[0].src_paths.return_value = src_paths # Create fake files to serve os.makedirs('src/subdir') expected_page = u'test \u023Frc file' self._create_fake_files(src_paths, expected_page) # Expect that the server sends us the files for path in src_paths: url = self.server.root_url() + 'suite/test-suite-0/include/' + path self._assert_page_equals(url, expected_page) def test_serve_spec_js(self): # Configure the suite description to contain JS spec files spec_paths = ['spec/1.js', 'spec/subdir/2.js'] self.suite_desc_list[0].spec_paths.return_value = spec_paths # Create fake files to serve os.makedirs('spec/subdir') expected_page = u'test spe\u023C file' self._create_fake_files(spec_paths, expected_page) # Expect that the server sends us the files for path in spec_paths: url = self.server.root_url() + 'suite/test-suite-0/include/' + path self._assert_page_equals(url, expected_page) def test_serve_text_fixtures(self): # Configure the suite description to contain test fixture files fixture_paths = ['fixtures/1.html', 'fixtures/subdir/2.html'] self.suite_desc_list[0].fixture_paths.return_value = fixture_paths # Create fake files to serve os.makedirs('fixtures/subdir') expected_page = u'test fi\u039Eture' self._create_fake_files(fixture_paths, expected_page) # Expect that the server sends us the files for path in fixture_paths: url = self.server.root_url() + 'suite/test-suite-0/include/' + path self._assert_page_equals(url, expected_page) def test_serve_binary_fixtures(self): # Configure the suite description to contain binary fixture files fixture_paths = ['fixtures/test.mp4', 'fixtures/test.png'] self.suite_desc_list[0].fixture_paths.return_value = fixture_paths # Create fake files to serve os.mkdir('fixtures') file_contents = '\x02\x03\x04\x05\x06' self._create_fake_files(fixture_paths, file_contents, encoding=None) # Expect that the server sends us the files as # an un-decoded byte stream for path in fixture_paths: url = self.server.root_url() + 'suite/test-suite-0/include/' + path self._assert_page_equals(url, file_contents, encoding=None) def test_serve_byte_range_requests(self): # Configure the suite description to contain a binary fixture file fixture_paths = ['fixtures/test.mp4'] self.suite_desc_list[0].fixture_paths.return_value = fixture_paths # Make this file fairly large, so we can access ranges of it file_size = 10000 # Create a fake file to serve os.mkdir('fixtures') file_contents = '\x01' * file_size self._create_fake_files(fixture_paths, file_contents, encoding=None) # Check for byte range support url = self.server.root_url() + 'suite/test-suite-0/include/fixtures/test.mp4' resp = requests.get(url, headers={'Range': None}) # Expect that the server supports byte ranges self.assertEqual(resp.status_code, 200) self.assertEqual(resp.headers.get('Accept-Ranges'), 'bytes') # Check that we can make requests for byte ranges # Examples taken from the RFC: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 test_cases = [ ('0-499', 0, 499), ('0-', 0, 9999), ('9000-9999', 9000, 9999), ('9500-', 9500, 9999), ('-500', 9500, 9999), ] for byte_range, content_start, content_end in test_cases: print "Sending byte range '{0}'".format(byte_range) resp = requests.get(url, headers={'Range': 'bytes=' + byte_range}) # Expect that we get a 206 (partial content) self.assertEqual(resp.status_code, 206) self.assertEqual( resp.headers.get('Content-Range'), 'bytes {0}-{1}/{2}'.format(content_start, content_end, file_size) ) content_len = content_end - content_start + 1 self.assertEqual(resp.headers.get('Content-Length'), str(content_len)) self.assertEqual(len(resp.content), content_len) def test_serve_multiple_byte_ranges(self): # Configure the suite description to contain a binary fixture file fixture_paths = ['fixtures/test.mp4'] self.suite_desc_list[0].fixture_paths.return_value = fixture_paths # Create a fake file to serve # The file has \x01 as the first byte, \x02 as the middle bytes # and \x03 as the last byte os.mkdir('fixtures') file_contents = '\x01' * 10 self._create_fake_files(fixture_paths, file_contents, encoding=None) # Make a request for multiple byte range byte_range = '0-3,4-7' url = self.server.root_url() + 'suite/test-suite-0/include/fixtures/test.mp4' resp = requests.get(url, headers={'Range': 'bytes=' + byte_range}) # Expect that the request for multiple ranges is ignored # and the whole file is returned # (we don't implement this part of the protocol) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content, file_contents) def test_invalid_byte_range(self): # Configure the suite description to contain a binary fixture file fixture_paths = ['fixtures/test.mp4'] self.suite_desc_list[0].fixture_paths.return_value = fixture_paths # Make this file fairly large, so we can access ranges of it file_size = 10000 # Create a fake file to serve os.mkdir('fixtures') file_contents = '\x01' * file_size self._create_fake_files(fixture_paths, file_contents, encoding=None) # Send invalid byte range headers and expect a 200 with the full file returned url = self.server.root_url() + 'suite/test-suite-0/include/fixtures/test.mp4' for invalid_range in ['not_bytes=0-10', 'bytes = space', 'bytes=text-text', 'bytes=-']: resp = requests.get(url, headers={'Range': invalid_range}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content, file_contents) def test_unsatisfiable_range(self): # Configure the suite description to contain a binary fixture file fixture_paths = ['fixtures/test.mp4'] self.suite_desc_list[0].fixture_paths.return_value = fixture_paths # Make this file fairly large, so we can access ranges of it file_size = 10000 # Create a fake file to serve os.mkdir('fixtures') file_contents = '\x01' * file_size self._create_fake_files(fixture_paths, file_contents, encoding=None) # Send unsatisfiable range (start > end) and expect a 406 url = self.server.root_url() + 'suite/test-suite-0/include/fixtures/test.mp4' resp = requests.get(url, headers={'Range': 'bytes=10-2'}) self.assertEqual(resp.status_code, 406) def test_serve_iso_encoded_dependency(self): # Configure the suite description to contain dependency files # that are ISO encoded dependencies = ['1.js', '2.js', '3.js', '4.js'] self.suite_desc_list[0].lib_paths.return_value = [dependencies[0]] self.suite_desc_list[0].src_paths.return_value = [dependencies[1]] self.suite_desc_list[0].spec_paths.return_value = [dependencies[2]] self.suite_desc_list[0].fixture_paths.return_value = [dependencies[3]] # Create fake files to serve with ISO-8859-1 chars page_contents = '\xf6 \x9a \xa0' self._create_fake_files(dependencies, page_contents, encoding=None) # Expect that the server sends us the files, # ignoring any GET parameters we pass in the URL expected_page = u'\xf6 \x9a \xa0' for path in dependencies: url = self.server.root_url() + 'suite/test-suite-0/include/' + path + "?123456" self._assert_page_equals(url, expected_page, encoding='iso-8859-1') def test_ignore_dependency_get_params(self): # Configure the suite description to contain dependency files dependencies = ['1.js', '2.js', '3.js', '4.js'] self.suite_desc_list[0].lib_paths.return_value = [dependencies[0]] self.suite_desc_list[0].src_paths.return_value = [dependencies[1]] self.suite_desc_list[0].spec_paths.return_value = [dependencies[2]] self.suite_desc_list[0].fixture_paths.return_value = [dependencies[3]] # Create fake files to serve expected_page = u'\u0236est dependency' self._create_fake_files(dependencies, expected_page) # Expect that the server sends us the files, # ignoring any GET parameters we pass in the URL for path in dependencies: url = self.server.root_url() + 'suite/test-suite-0/include/' + path + "?123456" self._assert_page_equals(url, expected_page) def test_different_working_dir(self): # Configure the suite description to contain JS dependencies spec_paths = ['spec/1.js'] self.suite_desc_list[0].spec_paths.return_value = spec_paths # Create fake files to serve os.makedirs('spec/subdir') expected_page = u'test spec file' self._create_fake_files(spec_paths, expected_page) # Should be able to change the working directory and still # get the dependencies, because the suite description # contains the root directory for dependency paths. # The superclass `TemplateWorkspaceTestCase` will reset the working # directory on `tearDown()` os.mkdir('different_dir') os.chdir('different_dir') # Expect that we still get the files for path in spec_paths: url = self.server.root_url() + 'suite/test-suite-0/include/' + path self._assert_page_equals(url, expected_page) def test_404_pages(self): # Try a URL that is not one of the suite urls root_url = self.server.root_url() bad_url_list = [root_url + 'invalid', root_url + 'runner/not_found.txt', root_url + 'suite/{}'.format(self.NUM_SUITE_DESC + 1), root_url + 'suite/{}'.format(-1)] # Expect that we get a page not found status for bad_url in bad_url_list: response = requests.get(bad_url) self.assertEqual(response.status_code, requests.codes.not_found, msg=bad_url) def test_missing_dependency(self): # Configure the suite description to contain a file self.suite_desc_list[0].src_paths.return_value = ['not_found.txt'] # The file does not exist, so expect that we # get a not found response response = requests.get(self.server.root_url() + 'not_found.txt') self.assertEqual(response.status_code, requests.codes.not_found) def _assert_page_equals(self, url, expected_content, encoding='utf-8'): """ Assert that the page at `url` contains `expected_content`. Uses a GET HTTP request to retrieve the page and expects a 200 status code, with UTF-8 encoding. `encoding` is the expected encoding. If None, expect an unencoded byte string. """ # HTTP GET request for the page response = requests.get(url) # Expect that we get a success result code self.assertEqual(response.status_code, requests.codes.ok, msg=url) # Expect that we got an accurate content length if encoding is None: expected_len = len(expected_content) else: expected_len = len(expected_content.encode(encoding)) self.assertEqual( str(expected_len), response.headers.get('content-length') ) # Expect that the content is what we rendered if encoding is not None: self.assertIn( expected_content, response.content.decode(encoding), msg=url ) # If no encoding, just expect the byte string else: self.assertIn(expected_content, response.content, msg=url) @staticmethod def _create_fake_files(path_list, contents, encoding='utf8'): """ For each path in `path_list`, create a file containing `contents` (a string). """ for path in path_list: with open(path, 'w') as fake_file: # If an encoding is specified, use it to convert # the string to a byte str if encoding is not None: encoded_contents = contents.encode(encoding) else: encoded_contents = contents # Write the byte string to the file fake_file.write(encoded_contents)