1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2020 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Tests for bisect_clang_crashes."""
8
9# pylint: disable=cros-logging-import
10import glob
11import logging
12import os.path
13import subprocess
14import unittest
15import unittest.mock as mock
16
17import bisect_clang_crashes
18
19
20class Test(unittest.TestCase):
21  """Tests for bisect_clang_crashes."""
22
23  class _SilencingFilter(object):
24    """Silences all log messages.
25
26    Also collects info about log messages that would've been emitted.
27    """
28
29    def __init__(self):
30      self.messages = []
31
32    def filter(self, record):
33      self.messages.append(record.getMessage())
34      return 0
35
36  @mock.patch.object(subprocess, 'check_output')
37  def test_get_artifacts(self, mock_gsutil_ls):
38    pattern = 'gs://chromeos-toolchain-artifacts/clang-crash-diagnoses/' \
39              '**/*clang_crash_diagnoses.tar.xz'
40    mock_gsutil_ls.return_value = 'artifact1\nartifact2\nartifact3'
41    results = bisect_clang_crashes.get_artifacts(pattern)
42    self.assertEqual(results, ['artifact1', 'artifact2', 'artifact3'])
43    mock_gsutil_ls.assert_called_once_with(['gsutil.py', 'ls', pattern],
44                                           stderr=subprocess.STDOUT,
45                                           encoding='utf-8')
46
47  @mock.patch.object(os.path, 'exists')
48  @mock.patch.object(glob, 'glob')
49  def test_get_crash_reproducers_succeed(self, mock_file_search,
50                                         mock_file_check):
51    working_dir = 'SomeDirectory'
52    mock_file_search.return_value = ['a.c', 'b.cpp', 'c.cc']
53    mock_file_check.side_effect = [True, True, True]
54    results = bisect_clang_crashes.get_crash_reproducers(working_dir)
55    mock_file_search.assert_called_once_with('%s/*.c*' % working_dir)
56    self.assertEqual(mock_file_check.call_count, 3)
57    self.assertEqual(mock_file_check.call_args_list[0], mock.call('a.sh'))
58    self.assertEqual(mock_file_check.call_args_list[1], mock.call('b.sh'))
59    self.assertEqual(mock_file_check.call_args_list[2], mock.call('c.sh'))
60    self.assertEqual(results, [('a.c', 'a.sh'), ('b.cpp', 'b.sh'),
61                               ('c.cc', 'c.sh')])
62
63  @mock.patch.object(os.path, 'exists')
64  @mock.patch.object(glob, 'glob')
65  def test_get_crash_reproducers_no_matching_script(self, mock_file_search,
66                                                    mock_file_check):
67
68    def silence_logging():
69      root = logging.getLogger()
70      filt = self._SilencingFilter()
71      root.addFilter(filt)
72      self.addCleanup(root.removeFilter, filt)
73      return filt
74
75    log_filter = silence_logging()
76    working_dir = 'SomeDirectory'
77    mock_file_search.return_value = ['a.c', 'b.cpp', 'c.cc']
78    mock_file_check.side_effect = [True, False, True]
79    results = bisect_clang_crashes.get_crash_reproducers(working_dir)
80    mock_file_search.assert_called_once_with('%s/*.c*' % working_dir)
81    self.assertEqual(mock_file_check.call_count, 3)
82    self.assertEqual(mock_file_check.call_args_list[0], mock.call('a.sh'))
83    self.assertEqual(mock_file_check.call_args_list[1], mock.call('b.sh'))
84    self.assertEqual(mock_file_check.call_args_list[2], mock.call('c.sh'))
85    self.assertEqual(results, [('a.c', 'a.sh'), ('c.cc', 'c.sh')])
86    self.assertTrue(
87        any('could not find the matching script of b.cpp' in x
88            for x in log_filter.messages), log_filter.messages)
89
90
91if __name__ == '__main__':
92  unittest.main()
93