Exemplo n.º 1
0
class TestAnalytics(unittest.TestCase):

    @classmethod
    def setUpClass(self):
        """ Load in the main pebble shell module """
        global root_dir
        self.root_dir = root_dir
        pebble_shell = imp.load_source('pebble_shell', 
                                       os.path.join(root_dir, 'pebble.py'))
        from pebble_shell import PbSDKShell
        self.p_sh = PbSDKShell()
        self.sdk_version = self.p_sh._get_version()
        
        # What directory is our data in?
        self.data_dir = os.path.join(os.path.dirname(__file__), 
                                     'analytics_data')
        
        # Create a temp directory to use
        self.tmp_dir = tempfile.mkdtemp()
        
        # Process command line options
        global g_cmd_args
        if g_cmd_args is not None:
            self.debug = g_cmd_args.debug
        else:
            self.debug = False
            
        # Setup the pebble command arguments
        self.pebble_cmd_line = ['pebble']
        if self.debug:
            self.pebble_cmd_line += ['--debug']
        
        # Delete the NO_TRACKING file if it exists
        self.no_tracking_file_path = os.path.normpath(os.path.join(root_dir, 
                            os.pardir, 'NO_TRACKING'))
        self.has_no_tracking_file = os.path.exists(self.no_tracking_file_path)
        if self.has_no_tracking_file:
            os.remove(self.no_tracking_file_path)

        # pre-cache path to pebble settings dir
        home_dir = os.path.expanduser("~")
        self.settings_dir = os.path.join(home_dir, ".pebble")

        # Create the set of common fields we expect to see in every event
        analytics = pebble.PblAnalytics._Analytics.get()
        client_id = open(os.path.join(self.settings_dir, 
                                 "client_id")).read()
        self.common_evt_fields = {
            'v': '1',
            'tid': analytics.tracking_id,
            'cid': client_id,
            'cn': re.escape(platform.platform()),
            'ck': re.escape(platform.python_version()),
            'cs': client_id,
        }
        
            
    def _printTestHeader(self):
        """ Print out what test we are running
        """
        print "\n###############################################################"
        print "Running test: %s.%s..." % (self.__class__, self._testMethodName)

    
    @classmethod
    def tearDownClass(self):
        """ Clean up after all tests """
        
        # Remove our temp directory
        shutil.rmtree(self.tmp_dir, ignore_errors=True)
        
        # Restore tracking file?
        if self.has_no_tracking_file:
            with open(self.no_tracking_file_path, 'w') as fd:
                fd.write(" ")
        

    def use_project(self, project_name):
        """ Copies project_name from the analytics_data subdirectory to a
        new temp directory, sets that as the current working dir, and returns 
        that path. 
        
        Parameters:
        -------------------------------------------------------------------
        project_name:     name of the project
        retval: path to project after copied into a temp location
        """
        working_dir = os.path.join(self.tmp_dir, project_name)
        if os.path.exists(working_dir):
            shutil.rmtree(working_dir)
        shutil.copytree(os.path.join(self.data_dir, project_name), 
                        working_dir)
        print "Running '%s' project in directory %s" % (project_name, 
                                                        working_dir)
        return working_dir


    
    def find_evt(self, mock_urlopen, items_filter):
        """ Walk through all the calls to our mock urlopen() and look for one 
        that satisfies items_filter, which is a dict with the key/value pairs we
        need to satisfy. The values are regular expressions.
        
        Parameters:
        -------------------------------------------------------------------
        mock_urlopen: the mock urlopen instance
        items_filter: dict with desired key/value pairs
        retval: (header, data) if event was found
                (None, None) if not found
        """

        found = False
        for call in mock_urlopen.call_args_list:
            req = call[0][0]
            if isinstance(req, urllib2.Request):
              header = req.headers
              data = urlparse.parse_qs(req.get_data())
              if self.debug:
                  print "Parsing event: %s" % (pprint.pformat(data))
              matches = True
              for (key, value) in items_filter.items():
                  if not re.match(value, data[key][0]):
                      matches = False
                      break
              if matches:
                  return (header, data)

        return (None, None)
    
    
    def assert_evt(self, mock_urlopen, items_filter, include_common=True):
        """ Walk through all the calls to our mock urlopen() and look for one 
        that satisfies items_filter, which is a dict with the key/value pairs we
        need to satisfy. The values are regular expressions.
        
        If not found, raise an assertion
        
        Parameters:
        -------------------------------------------------------------------
        mock_urlopen: the mock urlopen instance
        items_filter: dict with desired key/value pairs
        """
        if include_common:
            for (key, value) in self.common_evt_fields.items():
                if key in items_filter:
                    continue
                items_filter[key] = value
 
        (header, data) = self.find_evt(mock_urlopen, items_filter)
        self.assertIsNotNone(header, "Did not find expected event "
                        "matching constraints: %s" % (str(items_filter)))
        
        # Check the header contents
        self.assertTrue(header['User-agent'].startswith('Pebble SDK/%s' % 
                                                        (self.sdk_version)))
        

    @patch('pebble.PblAnalytics.urlopen')
    def test_missing_packages(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command  without necessary python packages installed
        """
        self._printTestHeader()
        
        # Run at least once to load all modules in
        sys.argv = self.pebble_cmd_line + ['clean' ]
        self.p_sh.main()

        # Unload the websocket package and remove it from sys.modules and
        #  clear out sys.path so we can't find it again. We have insured that
        #  the pebble.py module tries to import websocket
        sys.modules.pop('websocket')
        old_path = sys.path
        sys.path = []
        
        sys.argv = self.pebble_cmd_line + ['clean' ]
        try:
            new_shell_module = imp.load_source('new_shell_module', 
                                       os.path.join(root_dir, 'pebble.py'))
        except:
            pass
        
        # Restore sys.path
        sys.path = old_path
        
        # Verify that we sent a missing python dependency event
        self.assert_evt(mock_urlopen,
            {'ec': 'install', 'ea': 'import', 
             'el': 'fail: missing import:.*'})


    @patch('pebble.PblAnalytics.urlopen')
    def test_invalid_project(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command in an invalid project directory. 
        """
        self._printTestHeader()

        sys.argv = self.pebble_cmd_line + ['clean' ]
        retval = self.p_sh.main()
        
        # Verify that we sent an invalid project event
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'clean', 
             'el': 'fail: invalid project'})


    @patch('pebble.PblAnalytics.urlopen')
    def test_outdated_project(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command in an outdated project directory. 
        
        We made this project "outdated" by putting a resource_map.json
        file in the resources/src directory. 
        """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('outdated_project')
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build' ]
            retval = self.p_sh.main()
        
        # Verify that we sent an outdated project event
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'build', 
             'el': 'fail: outdated project'})


    @patch('pebble.PblAnalytics.urlopen')
    def test_app_too_big(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command in an app which is too big.
        
        We made this project "too big" by declaring a large static array
        in the main source file (hello_world.c):
           static int foo[25000] = {0};
        """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('too_big')
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build' ]
            retval = self.p_sh.main()
        
        # Verify that we sent a app too big event
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'build', 
             'el': 'fail: application too big'})


    @patch('pebble.PblAnalytics.urlopen')
    def test_compilation_error(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we build \
        an app with a compilation error
        """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        
        # Introduce a compilation error
        with open(os.path.join(working_dir, "src", "hello_world.c"), 'w') \
                    as fd:
            fd.write("foo")
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build' ]
            retval = self.p_sh.main()
        
        # Verify that we sent an compilation error event
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'build', 
             'el': 'fail: compilation error'})


    @patch('pebble.PblAnalytics.urlopen')
    def test_clean_success(self, mock_urlopen):
        """ Test for correct event sent in response to the 'clean' command """
        self._printTestHeader()
        
        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['clean' ]
            retval = self.p_sh.main()

        # Verify that we sent a success event
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'clean', 'el': 'success'})
        

    @patch('pebble.PblAnalytics.urlopen')
    def test_no_tracking_support(self, mock_urlopen):
        """ Test that we don't generate any events if we detect a 
        NO_TRACKING file"""
        self._printTestHeader()

        # Create a NO_TRACKING file and catch exceptions so that we
        #  are sure to delete it aftewards        
        try:
            with open(self.no_tracking_file_path, 'w') as fd:
                fd.write(" ")
            
            # Force a reload of the analytics instance
            pebble.PblAnalytics._Analytics.unload()
            
            # Copy the desired project to temp location
            working_dir = self.use_project('good_c_app')
                        
            with temp_chdir(working_dir):
                sys.argv = self.pebble_cmd_line + ['clean' ]
                retval = self.p_sh.main()
                
            call_count = mock_urlopen.call_count
            
        finally:
            # Undo tracking file
            os.remove(self.no_tracking_file_path)
            pebble.PblAnalytics._Analytics.unload()
                
        # Verify that no events were sent out
        self.assertEqual(call_count, 0, "Expected no URL "
            "requests with tracking off but got %d" % call_count)


    @patch('pebble.PblAnalytics.urlopen')
    def test_build_success_c_app(self, mock_urlopen):
        """ Test that we send the correct events after building a C app """
        self._printTestHeader()
        
        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        uuid = '19aac3eb-870b-47fb-a708-0810edc4322e'
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent the correct events
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'build', 'el': 'success'})
        
        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'totalSize', 'el': uuid, 'ev': '8.*'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appResources', 'ea': 'totalSize', 'el': uuid, 'ev': '0'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appResources', 'ea': 'totalCount', 'el': uuid, 'ev': '0'})

        for name in ['raw', 'image', 'font']:
            self.assert_evt(mock_urlopen,
                {'ec': 'appResources', 'ea': '%sSize' % (name), 'el': uuid, 
                 'ev': '0'})
    
            self.assert_evt(mock_urlopen,
                {'ec': 'appResources', 'ea': '%sCount' % (name), 'el': uuid, 
                 'ev': '0'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'cLineCount', 'el': uuid, 'ev': '108'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'jsLineCount', 'el': uuid, 'ev': '0'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'hasJavaScript', 'el': uuid, 'ev': '0'})



    @patch('pebble.PblAnalytics.urlopen')
    def test_build_success_js_app(self, mock_urlopen):
        """ Test that we send the correct events after building a JS app """
        self._printTestHeader()
        
        # Copy the desired project to temp location
        working_dir = self.use_project('good_js_app')
        uuid = '74460383-8a0f-4bb6-971f-8937c2ed4441'
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent the correct events
        self.assert_evt(mock_urlopen,
            {'ec': 'pebbleCmd', 'ea': 'build', 'el': 'success'})
        
        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'totalSize', 'el': uuid, 'ev': '15..'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appResources', 'ea': 'totalSize', 'el': uuid, 'ev': '16131'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appResources', 'ea': 'totalCount', 'el': uuid, 'ev': '7'})

        sizes = {'raw': '4961', 'image': '3888', 'font': '7282'}
        counts = {'raw': '2', 'image': '4', 'font': '1'}
        for name in ['raw', 'image', 'font']:
            self.assert_evt(mock_urlopen,
                {'ec': 'appResources', 'ea': '%sSize' % (name), 'el': uuid, 
                 'ev': sizes[name]})
    
            self.assert_evt(mock_urlopen,
                {'ec': 'appResources', 'ea': '%sCount' % (name), 'el': uuid, 
                 'ev': counts[name]})

        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'cLineCount', 'el': uuid, 'ev': '136'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'jsLineCount', 'el': uuid, 'ev': '107'})

        self.assert_evt(mock_urlopen,
            {'ec': 'appCode', 'ea': 'hasJavaScript', 'el': uuid, 'ev': '1'})


    def test_new_sdk_install(self):
        """ Test that we get the correct analytics produced when we run \
        a pebble command on a newly installed or recently upgraded SDK. 
        """
        self._printTestHeader()

        # Temporarily remove the .pebble directory
        save_settings_dir = self.settings_dir + ".bck"
        if os.path.exists(self.settings_dir):
            if os.path.exists(save_settings_dir):
                shutil.rmtree(save_settings_dir)
            os.rename(self.settings_dir, save_settings_dir)
        else:
            save_settings_dir = None
        
        # Force a re-instantiation of the Analytics object
        pebble.PblAnalytics._Analytics.unload()
        sys.argv = self.pebble_cmd_line + ['clean' ]
        with patch('pebble.PblAnalytics.urlopen') as mock_urlopen:
            self.p_sh.main()
    
            # Verify that a client id file and SDK version file got generated
            client_id = open(os.path.join(self.settings_dir, 
                                         "client_id")).read()            
            sdk_version = open(os.path.join(self.settings_dir, 
                                            "sdk_version")).read()

            # Verify that we got an install event
            self.assert_evt(mock_urlopen,
                {'ec': 'install', 'ea': 'firstTime', 'cid': client_id,
                 'cs': client_id})
        
        # Modify the SDK version, we should get an upgrade event and
        #  verify that the right client id got used
        with open(os.path.join(self.settings_dir, "sdk_version"), 'w') as fd:
            fd.write("foo")
        pebble.PblAnalytics._Analytics.unload()
        with patch('pebble.PblAnalytics.urlopen') as mock_urlopen:
            self.p_sh.main()
            self.assert_evt(mock_urlopen,
                {'ec': 'install', 'ea': 'upgrade', 'cid': client_id,
                 'cs': client_id})

        # Verify that the client_id file can have something like 'PEBBLE_INTERNAL'
        # in it
        with open(os.path.join(self.settings_dir, "client_id"), 'w') as fd:
            fd.write("PEBBLE_INTERNAL")
        pebble.PblAnalytics._Analytics.unload()
        with patch('pebble.PblAnalytics.urlopen') as mock_urlopen:
            self.p_sh.main()
            self.assert_evt(mock_urlopen,
                {'ec': 'pebbleCmd', 'ea': 'clean', 'cid': 'PEBBLE_INTERNAL',
                 'cs': 'PEBBLE_INTERNAL'})
            
        # Restore original .pebble dir            
        if save_settings_dir is not None:
            shutil.rmtree(self.settings_dir)
            os.rename(save_settings_dir, self.settings_dir)
        

    @patch('pebble.PblAnalytics.urlopen')
    def test_missing_tools(self, mock_urlopen):
        """ Test that we send the right event when the ARM tools are missing"""
        self._printTestHeader()
        
        # Rename the tools directory so that it can't be found
        tools_dir = os.path.join(root_dir, os.pardir, 'arm-cs-tools')
        save_tools_dir = tools_dir + ".bck"
        if os.path.exists(tools_dir):            
            os.rename(tools_dir, save_tools_dir)
        else:
            save_tools_dir = None
            
        # If we can still find it, remove it from the path
        save_os_environ = os.environ['PATH']
        paths = save_os_environ.split(':')
        while True: 
            where = sh.which('arm-none-eabi-size')
            if where is None:
                break
            dir = os.path.split(where)[0]
            paths.remove(dir)
            os.environ['PATH'] = ":".join(paths)
            
        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent missing tools event
        self.assert_evt(mock_urlopen,
            {'ec': 'install', 'ea': 'tools', 'el': 'fail: The compiler.*'})
        
        
        # Restore environment
        if save_tools_dir is not None:
            os.rename(save_tools_dir, tools_dir)
        os.environ['PATH'] = save_os_environ
        

    @patch('pebble.PblAnalytics.urlopen')
    def test_app_install(self, mock_urlopen):
        """ Test that we send the correct events when installing an app """
        self._printTestHeader()
        
        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        uuid = '19aac3eb-870b-47fb-a708-0810edc4322e'
        
        with contextlib.nested (
             patch('pebble.LibPebblesCommand.libpebble'),
             patch.object(pebble.LibPebblesCommand.LibPebbleCommand, 'tail')
             ) as (mock_libpebble, mock_tail):

            attrs = {'get_phone_info.return_value': 
                     'FakeOS,FakeOSVersion,FakeModel'}
            Pebble_mock = MagicMock(**attrs)
            mock_libpebble.Pebble = MagicMock(return_value=Pebble_mock)
            
            with temp_chdir(working_dir):
                sys.argv = self.pebble_cmd_line + ['build']
                self.p_sh.main()
                sys.argv = self.pebble_cmd_line + ['install']
                self.p_sh.main()
    
                # Verify that we sent the correct events
                self.assert_evt(mock_urlopen,
                    {'ec': 'pebbleCmd', 'ea': 'install', 'el': 'success'})
            
                self.assert_evt(mock_urlopen,
                    {'ec': 'phone', 'ea': 'os', 'el': 'FakeOS'})
            
                self.assert_evt(mock_urlopen,
                    {'ec': 'phone', 'ea': 'osVersion', 'el': 'FakeOSVersion'})
            
                self.assert_evt(mock_urlopen,
                    {'ec': 'phone', 'ea': 'model', 'el': 'FakeModel'})

                # Verify that install --logs produces the right action
                mock_urlopen.reset_mock()
                sys.argv = self.pebble_cmd_line + ['install', '--logs']
                self.p_sh.main()
                self.assert_evt(mock_urlopen,
                    {'ec': 'pebbleCmd', 'ea': re.escape('install --logs'), 
                     'el': 'success'})
Exemplo n.º 2
0
class TestProjects(unittest.TestCase):

    @classmethod
    def setUpClass(self):
        """ Load in the main pebble shell module """
        global root_dir
        self.root_dir = root_dir
        pebble_shell = imp.load_source('pebble_shell', 
                                       os.path.join(root_dir, 'pebble.py'))
        from pebble_shell import PbSDKShell
        self.p_sh = PbSDKShell()
        
        # What directory is our data in?
        self.data_dir = os.path.join(os.path.dirname(__file__), 
                                     'projects')
        
        # Create a temp directory to use
        self.tmp_dir = tempfile.mkdtemp()
        
        # Process command line options
        global g_cmd_args
        if g_cmd_args is not None:
            self.debug = g_cmd_args.debug
        else:
            self.debug = False
            
        # Setup the pebble command arguments
        self.pebble_cmd_line = ['pebble']
        if self.debug:
            self.pebble_cmd_line += ['--debug']
        
            
    def _printTestHeader(self):
        """ Print out what test we are running
        """
        print "\n###############################################################"
        print "Running test: %s.%s..." % (self.__class__, self._testMethodName)

    
    @classmethod
    def tearDownClass(self):
        """ Clean up after all tests """
        
        # Remove our temp directory
        shutil.rmtree(self.tmp_dir, ignore_errors=True)
                

    def use_project(self, project_name):
        """ Copies project_name from the data subdirectory to a
        new temp directory, sets that as the current working dir, and returns 
        that path. 
        
        Parameters:
        -------------------------------------------------------------------
        project_name:     name of the project
        retval: path to project after copied into a temp location
        """
        working_dir = os.path.join(self.tmp_dir, project_name)
        if os.path.exists(working_dir):
            shutil.rmtree(working_dir)
        shutil.copytree(os.path.join(self.data_dir, project_name), 
                        working_dir)
        print "Running '%s' project in directory %s" % (project_name, 
                                                        working_dir)
        return working_dir


    def test_with_spaces(self):
        """ Test for correct behavior when the project path has spaces in it"""
        self._printTestHeader()
        
        # Copy the desired project to temp location
        working_dir = self.use_project('with spaces')
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build' ]
            retval = self.p_sh.main()

        # Verify that we sent a success event
        self.assertEqual(retval, 0)
        

    def test_convert(self):
        """ Test for correct behavior during project conversion from 1.x to
        2.x """
        self._printTestHeader()
        
        # Copy the desired project to temp location
        working_dir = self.use_project('needs_convert')
        
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['convert-project' ]
            retval = self.p_sh.main()

        # Verify that we sent a success event
        self.assertEqual(retval, 0)
Exemplo n.º 3
0
class TestAnalytics(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        """ Load in the main pebble shell module """
        global root_dir
        self.root_dir = root_dir
        pebble_shell = imp.load_source('pebble_shell',
                                       os.path.join(root_dir, 'pebble.py'))
        from pebble_shell import PbSDKShell
        self.p_sh = PbSDKShell()
        self.sdk_version = self.p_sh._get_version()

        # What directory is our data in?
        self.data_dir = os.path.join(os.path.dirname(__file__),
                                     'analytics_data')

        # Create a temp directory to use
        self.tmp_dir = tempfile.mkdtemp()

        # Process command line options
        global g_cmd_args
        if g_cmd_args is not None:
            self.debug = g_cmd_args.debug
        else:
            self.debug = False

        # Setup the pebble command arguments
        self.pebble_cmd_line = ['pebble']
        if self.debug:
            self.pebble_cmd_line += ['--debug']

        # Delete the NO_TRACKING file if it exists
        self.no_tracking_file_path = os.path.normpath(
            os.path.join(root_dir, os.pardir, 'NO_TRACKING'))
        self.has_no_tracking_file = os.path.exists(self.no_tracking_file_path)
        if self.has_no_tracking_file:
            os.remove(self.no_tracking_file_path)

        # pre-cache path to pebble settings dir
        home_dir = os.path.expanduser("~")
        self.settings_dir = os.path.join(home_dir, ".pebble")

        # Create the set of common fields we expect to see in every event
        analytics = pebble.PblAnalytics._Analytics.get()
        client_id = open(os.path.join(self.settings_dir, "client_id")).read()
        self.common_evt_fields = {
            'v': '1',
            'tid': analytics.tracking_id,
            'cid': client_id,
            'cn': re.escape(platform.platform()),
            'ck': re.escape(platform.python_version()),
            'cs': client_id,
        }

    def _printTestHeader(self):
        """ Print out what test we are running
        """
        print "\n###############################################################"
        print "Running test: %s.%s..." % (self.__class__, self._testMethodName)

    @classmethod
    def tearDownClass(self):
        """ Clean up after all tests """

        # Remove our temp directory
        shutil.rmtree(self.tmp_dir, ignore_errors=True)

        # Restore tracking file?
        if self.has_no_tracking_file:
            with open(self.no_tracking_file_path, 'w') as fd:
                fd.write(" ")

    def use_project(self, project_name):
        """ Copies project_name from the analytics_data subdirectory to a
        new temp directory, sets that as the current working dir, and returns 
        that path. 
        
        Parameters:
        -------------------------------------------------------------------
        project_name:     name of the project
        retval: path to project after copied into a temp location
        """
        working_dir = os.path.join(self.tmp_dir, project_name)
        if os.path.exists(working_dir):
            shutil.rmtree(working_dir)
        shutil.copytree(os.path.join(self.data_dir, project_name), working_dir)
        print "Running '%s' project in directory %s" % (project_name,
                                                        working_dir)
        return working_dir

    def find_evt(self, mock_urlopen, items_filter):
        """ Walk through all the calls to our mock urlopen() and look for one 
        that satisfies items_filter, which is a dict with the key/value pairs we
        need to satisfy. The values are regular expressions.
        
        Parameters:
        -------------------------------------------------------------------
        mock_urlopen: the mock urlopen instance
        items_filter: dict with desired key/value pairs
        retval: (header, data) if event was found
                (None, None) if not found
        """

        found = False
        for call in mock_urlopen.call_args_list:
            req = call[0][0]
            if isinstance(req, urllib2.Request):
                header = req.headers
                data = urlparse.parse_qs(req.get_data())
                if self.debug:
                    print "Parsing event: %s" % (pprint.pformat(data))
                matches = True
                for (key, value) in items_filter.items():
                    if not re.match(value, data[key][0]):
                        matches = False
                        break
                if matches:
                    return (header, data)

        return (None, None)

    def assert_evt(self, mock_urlopen, items_filter, include_common=True):
        """ Walk through all the calls to our mock urlopen() and look for one 
        that satisfies items_filter, which is a dict with the key/value pairs we
        need to satisfy. The values are regular expressions.
        
        If not found, raise an assertion
        
        Parameters:
        -------------------------------------------------------------------
        mock_urlopen: the mock urlopen instance
        items_filter: dict with desired key/value pairs
        """
        if include_common:
            for (key, value) in self.common_evt_fields.items():
                if key in items_filter:
                    continue
                items_filter[key] = value

        (header, data) = self.find_evt(mock_urlopen, items_filter)
        self.assertIsNotNone(
            header, "Did not find expected event "
            "matching constraints: %s" % (str(items_filter)))

        # Check the header contents
        self.assertTrue(header['User-agent'].startswith('Pebble SDK/%s' %
                                                        (self.sdk_version)))

    @patch('pebble.PblAnalytics.urlopen')
    def test_missing_packages(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command  without necessary python packages installed
        """
        self._printTestHeader()

        # Run at least once to load all modules in
        sys.argv = self.pebble_cmd_line + ['clean']
        self.p_sh.main()

        # Unload the websocket package and remove it from sys.modules and
        #  clear out sys.path so we can't find it again. We have insured that
        #  the pebble.py module tries to import websocket
        sys.modules.pop('websocket')
        old_path = sys.path
        sys.path = []

        sys.argv = self.pebble_cmd_line + ['clean']
        try:
            new_shell_module = imp.load_source(
                'new_shell_module', os.path.join(root_dir, 'pebble.py'))
        except:
            pass

        # Restore sys.path
        sys.path = old_path

        # Verify that we sent a missing python dependency event
        self.assert_evt(mock_urlopen, {
            'ec': 'install',
            'ea': 'import',
            'el': 'fail: missing import:.*'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_invalid_project(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command in an invalid project directory. 
        """
        self._printTestHeader()

        sys.argv = self.pebble_cmd_line + ['clean']
        retval = self.p_sh.main()

        # Verify that we sent an invalid project event
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'clean',
            'el': 'fail: invalid project'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_outdated_project(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command in an outdated project directory. 
        
        We made this project "outdated" by putting a resource_map.json
        file in the resources/src directory. 
        """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('outdated_project')

        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent an outdated project event
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'build',
            'el': 'fail: outdated project'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_app_too_big(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we run \
        a pebble command in an app which is too big.
        
        We made this project "too big" by declaring a large static array
        in the main source file (hello_world.c):
           static int foo[25000] = {0};
        """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('too_big')

        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent a app too big event
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'build',
            'el': 'fail: application too big'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_compilation_error(self, mock_urlopen):
        """ Test that we get the correct analytics produced when we build \
        an app with a compilation error
        """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')

        # Introduce a compilation error
        with open(os.path.join(working_dir, "src", "hello_world.c"), 'w') \
                    as fd:
            fd.write("foo")

        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent an compilation error event
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'build',
            'el': 'fail: compilation error'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_clean_success(self, mock_urlopen):
        """ Test for correct event sent in response to the 'clean' command """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')

        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['clean']
            retval = self.p_sh.main()

        # Verify that we sent a success event
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'clean',
            'el': 'success'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_no_tracking_support(self, mock_urlopen):
        """ Test that we don't generate any events if we detect a 
        NO_TRACKING file"""
        self._printTestHeader()

        # Create a NO_TRACKING file and catch exceptions so that we
        #  are sure to delete it aftewards
        try:
            with open(self.no_tracking_file_path, 'w') as fd:
                fd.write(" ")

            # Force a reload of the analytics instance
            pebble.PblAnalytics._Analytics.unload()

            # Copy the desired project to temp location
            working_dir = self.use_project('good_c_app')

            with temp_chdir(working_dir):
                sys.argv = self.pebble_cmd_line + ['clean']
                retval = self.p_sh.main()

            call_count = mock_urlopen.call_count

        finally:
            # Undo tracking file
            os.remove(self.no_tracking_file_path)
            pebble.PblAnalytics._Analytics.unload()

        # Verify that no events were sent out
        self.assertEqual(
            call_count, 0, "Expected no URL "
            "requests with tracking off but got %d" % call_count)

    @patch('pebble.PblAnalytics.urlopen')
    def test_build_success_c_app(self, mock_urlopen):
        """ Test that we send the correct events after building a C app """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        uuid = '19aac3eb-870b-47fb-a708-0810edc4322e'

        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent the correct events
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'build',
            'el': 'success'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'totalSize',
            'el': uuid,
            'ev': '8.*'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appResources',
            'ea': 'totalSize',
            'el': uuid,
            'ev': '0'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appResources',
            'ea': 'totalCount',
            'el': uuid,
            'ev': '0'
        })

        for name in ['raw', 'image', 'font']:
            self.assert_evt(
                mock_urlopen, {
                    'ec': 'appResources',
                    'ea': '%sSize' % (name),
                    'el': uuid,
                    'ev': '0'
                })

            self.assert_evt(
                mock_urlopen, {
                    'ec': 'appResources',
                    'ea': '%sCount' % (name),
                    'el': uuid,
                    'ev': '0'
                })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'cLineCount',
            'el': uuid,
            'ev': '108'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'jsLineCount',
            'el': uuid,
            'ev': '0'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'hasJavaScript',
            'el': uuid,
            'ev': '0'
        })

    @patch('pebble.PblAnalytics.urlopen')
    def test_build_success_js_app(self, mock_urlopen):
        """ Test that we send the correct events after building a JS app """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('good_js_app')
        uuid = '74460383-8a0f-4bb6-971f-8937c2ed4441'

        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent the correct events
        self.assert_evt(mock_urlopen, {
            'ec': 'pebbleCmd',
            'ea': 'build',
            'el': 'success'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'totalSize',
            'el': uuid,
            'ev': '15..'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appResources',
            'ea': 'totalSize',
            'el': uuid,
            'ev': '16131'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appResources',
            'ea': 'totalCount',
            'el': uuid,
            'ev': '7'
        })

        sizes = {'raw': '4961', 'image': '3888', 'font': '7282'}
        counts = {'raw': '2', 'image': '4', 'font': '1'}
        for name in ['raw', 'image', 'font']:
            self.assert_evt(
                mock_urlopen, {
                    'ec': 'appResources',
                    'ea': '%sSize' % (name),
                    'el': uuid,
                    'ev': sizes[name]
                })

            self.assert_evt(
                mock_urlopen, {
                    'ec': 'appResources',
                    'ea': '%sCount' % (name),
                    'el': uuid,
                    'ev': counts[name]
                })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'cLineCount',
            'el': uuid,
            'ev': '136'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'jsLineCount',
            'el': uuid,
            'ev': '107'
        })

        self.assert_evt(mock_urlopen, {
            'ec': 'appCode',
            'ea': 'hasJavaScript',
            'el': uuid,
            'ev': '1'
        })

    def test_new_sdk_install(self):
        """ Test that we get the correct analytics produced when we run \
        a pebble command on a newly installed or recently upgraded SDK. 
        """
        self._printTestHeader()

        # Temporarily remove the .pebble directory
        save_settings_dir = self.settings_dir + ".bck"
        if os.path.exists(self.settings_dir):
            if os.path.exists(save_settings_dir):
                shutil.rmtree(save_settings_dir)
            os.rename(self.settings_dir, save_settings_dir)
        else:
            save_settings_dir = None

        # Force a re-instantiation of the Analytics object
        pebble.PblAnalytics._Analytics.unload()
        sys.argv = self.pebble_cmd_line + ['clean']
        with patch('pebble.PblAnalytics.urlopen') as mock_urlopen:
            self.p_sh.main()

            # Verify that a client id file and SDK version file got generated
            client_id = open(os.path.join(self.settings_dir,
                                          "client_id")).read()
            sdk_version = open(os.path.join(self.settings_dir,
                                            "sdk_version")).read()

            # Verify that we got an install event
            self.assert_evt(
                mock_urlopen, {
                    'ec': 'install',
                    'ea': 'firstTime',
                    'cid': client_id,
                    'cs': client_id
                })

        # Modify the SDK version, we should get an upgrade event and
        #  verify that the right client id got used
        with open(os.path.join(self.settings_dir, "sdk_version"), 'w') as fd:
            fd.write("foo")
        pebble.PblAnalytics._Analytics.unload()
        with patch('pebble.PblAnalytics.urlopen') as mock_urlopen:
            self.p_sh.main()
            self.assert_evt(
                mock_urlopen, {
                    'ec': 'install',
                    'ea': 'upgrade',
                    'cid': client_id,
                    'cs': client_id
                })

        # Verify that the client_id file can have something like 'PEBBLE_INTERNAL'
        # in it
        with open(os.path.join(self.settings_dir, "client_id"), 'w') as fd:
            fd.write("PEBBLE_INTERNAL")
        pebble.PblAnalytics._Analytics.unload()
        with patch('pebble.PblAnalytics.urlopen') as mock_urlopen:
            self.p_sh.main()
            self.assert_evt(
                mock_urlopen, {
                    'ec': 'pebbleCmd',
                    'ea': 'clean',
                    'cid': 'PEBBLE_INTERNAL',
                    'cs': 'PEBBLE_INTERNAL'
                })

        # Restore original .pebble dir
        if save_settings_dir is not None:
            shutil.rmtree(self.settings_dir)
            os.rename(save_settings_dir, self.settings_dir)

    @patch('pebble.PblAnalytics.urlopen')
    def test_missing_tools(self, mock_urlopen):
        """ Test that we send the right event when the ARM tools are missing"""
        self._printTestHeader()

        # Rename the tools directory so that it can't be found
        tools_dir = os.path.join(root_dir, os.pardir, 'arm-cs-tools')
        save_tools_dir = tools_dir + ".bck"
        if os.path.exists(tools_dir):
            os.rename(tools_dir, save_tools_dir)
        else:
            save_tools_dir = None

        # If we can still find it, remove it from the path
        save_os_environ = os.environ['PATH']
        paths = save_os_environ.split(':')
        while True:
            where = sh.which('arm-none-eabi-size')
            if where is None:
                break
            dir = os.path.split(where)[0]
            paths.remove(dir)
            os.environ['PATH'] = ":".join(paths)

        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        with temp_chdir(working_dir):
            sys.argv = self.pebble_cmd_line + ['build']
            retval = self.p_sh.main()

        # Verify that we sent missing tools event
        self.assert_evt(mock_urlopen, {
            'ec': 'install',
            'ea': 'tools',
            'el': 'fail: The compiler.*'
        })

        # Restore environment
        if save_tools_dir is not None:
            os.rename(save_tools_dir, tools_dir)
        os.environ['PATH'] = save_os_environ

    @patch('pebble.PblAnalytics.urlopen')
    def test_app_install(self, mock_urlopen):
        """ Test that we send the correct events when installing an app """
        self._printTestHeader()

        # Copy the desired project to temp location
        working_dir = self.use_project('good_c_app')
        uuid = '19aac3eb-870b-47fb-a708-0810edc4322e'

        with contextlib.nested(
                patch('pebble.LibPebblesCommand.libpebble'),
                patch.object(pebble.LibPebblesCommand.LibPebbleCommand,
                             'tail')) as (mock_libpebble, mock_tail):

            attrs = {
                'get_phone_info.return_value': 'FakeOS,FakeOSVersion,FakeModel'
            }
            Pebble_mock = MagicMock(**attrs)
            mock_libpebble.Pebble = MagicMock(return_value=Pebble_mock)

            with temp_chdir(working_dir):
                sys.argv = self.pebble_cmd_line + ['build']
                self.p_sh.main()
                sys.argv = self.pebble_cmd_line + ['install']
                self.p_sh.main()

                # Verify that we sent the correct events
                self.assert_evt(mock_urlopen, {
                    'ec': 'pebbleCmd',
                    'ea': 'install',
                    'el': 'success'
                })

                self.assert_evt(mock_urlopen, {
                    'ec': 'phone',
                    'ea': 'os',
                    'el': 'FakeOS'
                })

                self.assert_evt(mock_urlopen, {
                    'ec': 'phone',
                    'ea': 'osVersion',
                    'el': 'FakeOSVersion'
                })

                self.assert_evt(mock_urlopen, {
                    'ec': 'phone',
                    'ea': 'model',
                    'el': 'FakeModel'
                })

                # Verify that install --logs produces the right action
                mock_urlopen.reset_mock()
                sys.argv = self.pebble_cmd_line + ['install', '--logs']
                self.p_sh.main()
                self.assert_evt(
                    mock_urlopen, {
                        'ec': 'pebbleCmd',
                        'ea': re.escape('install --logs'),
                        'el': 'success'
                    })