/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/__init__.py

  • Committer: Aaron Bentley
  • Date: 2007-04-02 17:18:11 UTC
  • mfrom: (2392 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2393.
  • Revision ID: abentley@panoramicfeedback.com-20070402171811-bz7y2b2h1m1k4n7x
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
180
180
        self.num_tests = num_tests
181
181
        self.error_count = 0
182
182
        self.failure_count = 0
 
183
        self.known_failure_count = 0
183
184
        self.skip_count = 0
 
185
        self.unsupported = {}
184
186
        self.count = 0
185
187
        self._overall_start_time = time.time()
186
188
    
221
223
        """Record that a test has started."""
222
224
        self._start_time = time.time()
223
225
 
 
226
    def _cleanupLogFile(self, test):
 
227
        # We can only do this if we have one of our TestCases, not if
 
228
        # we have a doctest.
 
229
        setKeepLogfile = getattr(test, 'setKeepLogfile', None)
 
230
        if setKeepLogfile is not None:
 
231
            setKeepLogfile()
 
232
 
224
233
    def addError(self, test, err):
 
234
        self.extractBenchmarkTime(test)
 
235
        self._cleanupLogFile(test)
225
236
        if isinstance(err[1], TestSkipped):
226
 
            return self.addSkipped(test, err)    
 
237
            return self.addSkipped(test, err)
 
238
        elif isinstance(err[1], UnavailableFeature):
 
239
            return self.addNotSupported(test, err[1].args[0])
227
240
        unittest.TestResult.addError(self, test, err)
228
 
        # We can only do this if we have one of our TestCases, not if
229
 
        # we have a doctest.
230
 
        setKeepLogfile = getattr(test, 'setKeepLogfile', None)
231
 
        if setKeepLogfile is not None:
232
 
            setKeepLogfile()
233
 
        self.extractBenchmarkTime(test)
 
241
        self.error_count += 1
234
242
        self.report_error(test, err)
235
243
        if self.stop_early:
236
244
            self.stop()
237
245
 
238
246
    def addFailure(self, test, err):
 
247
        self._cleanupLogFile(test)
 
248
        self.extractBenchmarkTime(test)
 
249
        if isinstance(err[1], KnownFailure):
 
250
            return self.addKnownFailure(test, err)
239
251
        unittest.TestResult.addFailure(self, test, err)
240
 
        # We can only do this if we have one of our TestCases, not if
241
 
        # we have a doctest.
242
 
        setKeepLogfile = getattr(test, 'setKeepLogfile', None)
243
 
        if setKeepLogfile is not None:
244
 
            setKeepLogfile()
245
 
        self.extractBenchmarkTime(test)
 
252
        self.failure_count += 1
246
253
        self.report_failure(test, err)
247
254
        if self.stop_early:
248
255
            self.stop()
249
256
 
 
257
    def addKnownFailure(self, test, err):
 
258
        self.known_failure_count += 1
 
259
        self.report_known_failure(test, err)
 
260
 
 
261
    def addNotSupported(self, test, feature):
 
262
        self.unsupported.setdefault(str(feature), 0)
 
263
        self.unsupported[str(feature)] += 1
 
264
        self.report_unsupported(test, feature)
 
265
 
250
266
    def addSuccess(self, test):
251
267
        self.extractBenchmarkTime(test)
252
268
        if self._bench_history is not None:
258
274
        unittest.TestResult.addSuccess(self, test)
259
275
 
260
276
    def addSkipped(self, test, skip_excinfo):
261
 
        self.extractBenchmarkTime(test)
262
277
        self.report_skip(test, skip_excinfo)
263
278
        # seems best to treat this as success from point-of-view of unittest
264
279
        # -- it actually does nothing so it barely matters :)
301
316
class TextTestResult(ExtendedTestResult):
302
317
    """Displays progress and results of tests in text form"""
303
318
 
304
 
    def __init__(self, *args, **kw):
305
 
        ExtendedTestResult.__init__(self, *args, **kw)
306
 
        self.pb = self.ui.nested_progress_bar()
 
319
    def __init__(self, stream, descriptions, verbosity,
 
320
                 bench_history=None,
 
321
                 num_tests=None,
 
322
                 pb=None,
 
323
                 ):
 
324
        ExtendedTestResult.__init__(self, stream, descriptions, verbosity,
 
325
            bench_history, num_tests)
 
326
        if pb is None:
 
327
            self.pb = self.ui.nested_progress_bar()
 
328
            self._supplied_pb = False
 
329
        else:
 
330
            self.pb = pb
 
331
            self._supplied_pb = True
307
332
        self.pb.show_pct = False
308
333
        self.pb.show_spinner = False
309
 
        self.pb.show_eta = False, 
 
334
        self.pb.show_eta = False,
310
335
        self.pb.show_count = False
311
336
        self.pb.show_bar = False
312
337
 
322
347
            a += ', %d errors' % self.error_count
323
348
        if self.failure_count:
324
349
            a += ', %d failed' % self.failure_count
 
350
        if self.known_failure_count:
 
351
            a += ', %d known failures' % self.known_failure_count
325
352
        if self.skip_count:
326
353
            a += ', %d skipped' % self.skip_count
 
354
        if self.unsupported:
 
355
            a += ', %d missing features' % len(self.unsupported)
327
356
        a += ']'
328
357
        return a
329
358
 
342
371
            return self._shortened_test_description(test)
343
372
 
344
373
    def report_error(self, test, err):
345
 
        self.error_count += 1
346
374
        self.pb.note('ERROR: %s\n    %s\n', 
347
375
            self._test_description(test),
348
376
            err[1],
349
377
            )
350
378
 
351
379
    def report_failure(self, test, err):
352
 
        self.failure_count += 1
353
380
        self.pb.note('FAIL: %s\n    %s\n', 
354
381
            self._test_description(test),
355
382
            err[1],
356
383
            )
357
384
 
 
385
    def report_known_failure(self, test, err):
 
386
        self.pb.note('XFAIL: %s\n%s\n',
 
387
            self._test_description(test), err[1])
 
388
 
358
389
    def report_skip(self, test, skip_excinfo):
359
390
        self.skip_count += 1
360
391
        if False:
371
402
                # progress bar...
372
403
                self.pb.note('SKIP: %s', skip_excinfo[1])
373
404
 
 
405
    def report_unsupported(self, test, feature):
 
406
        """test cannot be run because feature is missing."""
 
407
                  
374
408
    def report_cleaning_up(self):
375
409
        self.pb.update('cleaning up...')
376
410
 
377
411
    def finished(self):
378
 
        self.pb.finished()
 
412
        if not self._supplied_pb:
 
413
            self.pb.finished()
379
414
 
380
415
 
381
416
class VerboseTestResult(ExtendedTestResult):
414
449
        return '%s%s' % (indent, err[1])
415
450
 
416
451
    def report_error(self, test, err):
417
 
        self.error_count += 1
418
452
        self.stream.writeln('ERROR %s\n%s'
419
453
                % (self._testTimeString(),
420
454
                   self._error_summary(err)))
421
455
 
422
456
    def report_failure(self, test, err):
423
 
        self.failure_count += 1
424
457
        self.stream.writeln(' FAIL %s\n%s'
425
458
                % (self._testTimeString(),
426
459
                   self._error_summary(err)))
427
460
 
 
461
    def report_known_failure(self, test, err):
 
462
        self.stream.writeln('XFAIL %s\n%s'
 
463
                % (self._testTimeString(),
 
464
                   self._error_summary(err)))
 
465
 
428
466
    def report_success(self, test):
429
467
        self.stream.writeln('   OK %s' % self._testTimeString())
430
468
        for bench_called, stats in getattr(test, '_benchcalls', []):
431
469
            self.stream.writeln('LSProf output for %s(%s, %s)' % bench_called)
432
470
            stats.pprint(file=self.stream)
 
471
        # flush the stream so that we get smooth output. This verbose mode is
 
472
        # used to show the output in PQM.
433
473
        self.stream.flush()
434
474
 
435
475
    def report_skip(self, test, skip_excinfo):
438
478
                % (self._testTimeString(),
439
479
                   self._error_summary(skip_excinfo)))
440
480
 
 
481
    def report_unsupported(self, test, feature):
 
482
        """test cannot be run because feature is missing."""
 
483
        self.stream.writeln("NODEP %s\n    The feature '%s' is not available."
 
484
                %(self._testTimeString(), feature))
 
485
                  
 
486
 
441
487
 
442
488
class TextTestRunner(object):
443
489
    stop_on_failure = False
486
532
            if errored:
487
533
                if failed: self.stream.write(", ")
488
534
                self.stream.write("errors=%d" % errored)
 
535
            if result.known_failure_count:
 
536
                if failed or errored: self.stream.write(", ")
 
537
                self.stream.write("known_failure_count=%d" %
 
538
                    result.known_failure_count)
489
539
            self.stream.writeln(")")
490
540
        else:
491
 
            self.stream.writeln("OK")
 
541
            if result.known_failure_count:
 
542
                self.stream.writeln("OK (known_failures=%d)" %
 
543
                    result.known_failure_count)
 
544
            else:
 
545
                self.stream.writeln("OK")
492
546
        if result.skip_count > 0:
493
547
            skipped = result.skip_count
494
548
            self.stream.writeln('%d test%s skipped' %
495
549
                                (skipped, skipped != 1 and "s" or ""))
 
550
        if result.unsupported:
 
551
            for feature, count in sorted(result.unsupported.items()):
 
552
                self.stream.writeln("Missing feature '%s' skipped %d tests." %
 
553
                    (feature, count))
496
554
        result.report_cleaning_up()
497
555
        # This is still a little bogus, 
498
556
        # but only a little. Folk not using our testrunner will
545
603
    """Indicates that a test was intentionally skipped, rather than failing."""
546
604
 
547
605
 
 
606
class KnownFailure(AssertionError):
 
607
    """Indicates that a test failed in a precisely expected manner.
 
608
 
 
609
    Such failures dont block the whole test suite from passing because they are
 
610
    indicators of partially completed code or of future work. We have an
 
611
    explicit error for them so that we can ensure that they are always visible:
 
612
    KnownFailures are always shown in the output of bzr selftest.
 
613
    """
 
614
 
 
615
 
 
616
class UnavailableFeature(Exception):
 
617
    """A feature required for this test was not available.
 
618
 
 
619
    The feature should be used to construct the exception.
 
620
    """
 
621
 
 
622
 
548
623
class CommandFailed(Exception):
549
624
    pass
550
625
 
793
868
                excName = str(excClass)
794
869
            raise self.failureException, "%s not raised" % excName
795
870
 
 
871
    def assertRaises(self, excClass, func, *args, **kwargs):
 
872
        """Assert that a callable raises a particular exception.
 
873
 
 
874
        :param excClass: As for the except statement, this may be either an
 
875
        exception class, or a tuple of classes.
 
876
 
 
877
        Returns the exception so that you can examine it.
 
878
        """
 
879
        try:
 
880
            func(*args, **kwargs)
 
881
        except excClass, e:
 
882
            return e
 
883
        else:
 
884
            if getattr(excClass,'__name__', None) is not None:
 
885
                excName = excClass.__name__
 
886
            else:
 
887
                # probably a tuple
 
888
                excName = str(excClass)
 
889
            raise self.failureException, "%s not raised" % excName
 
890
 
796
891
    def assertIs(self, left, right, message=None):
797
892
        if not (left is right):
798
893
            if message is not None:
970
1065
    def _restoreHooks(self):
971
1066
        bzrlib.branch.Branch.hooks = self._preserved_hooks
972
1067
 
 
1068
    def knownFailure(self, reason):
 
1069
        """This test has failed for some known reason."""
 
1070
        raise KnownFailure(reason)
 
1071
 
 
1072
    def run(self, result=None):
 
1073
        if result is None: result = self.defaultTestResult()
 
1074
        for feature in getattr(self, '_test_needs_features', []):
 
1075
            if not feature.available():
 
1076
                result.startTest(self)
 
1077
                if getattr(result, 'addNotSupported', None):
 
1078
                    result.addNotSupported(self, feature)
 
1079
                else:
 
1080
                    result.addSuccess(self)
 
1081
                result.stopTest(self)
 
1082
                return
 
1083
        return unittest.TestCase.run(self, result)
 
1084
 
973
1085
    def tearDown(self):
974
1086
        self._runCleanups()
975
1087
        unittest.TestCase.tearDown(self)
1045
1157
        """Shortcut that splits cmd into words, runs, and returns stdout"""
1046
1158
        return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
1047
1159
 
 
1160
    def requireFeature(self, feature):
 
1161
        """This test requires a specific feature is available.
 
1162
 
 
1163
        :raises UnavailableFeature: When feature is not available.
 
1164
        """
 
1165
        if not feature.available():
 
1166
            raise UnavailableFeature(feature)
 
1167
 
1048
1168
    def run_bzr_captured(self, argv, retcode=0, encoding=None, stdin=None,
1049
1169
                         working_dir=None):
1050
1170
        """Invoke bzr and return (stdout, stderr).
1383
1503
                    this_tree=wt_to)
1384
1504
        wt_to.add_parent_tree_id(branch_from.last_revision())
1385
1505
 
 
1506
    def reduceLockdirTimeout(self):
 
1507
        """Reduce the default lock timeout for the duration of the test, so that
 
1508
        if LockContention occurs during a test, it does so quickly.
 
1509
 
 
1510
        Tests that expect to provoke LockContention errors should call this.
 
1511
        """
 
1512
        orig_timeout = bzrlib.lockdir._DEFAULT_TIMEOUT_SECONDS
 
1513
        def resetTimeout():
 
1514
            bzrlib.lockdir._DEFAULT_TIMEOUT_SECONDS = orig_timeout
 
1515
        self.addCleanup(resetTimeout)
 
1516
        bzrlib.lockdir._DEFAULT_TIMEOUT_SECONDS = 0
1386
1517
 
1387
1518
BzrTestBase = TestCase
1388
1519
 
1412
1543
        # execution. Variables that the parameteriser sets need to be 
1413
1544
        # ones that are not set by setUp, or setUp will trash them.
1414
1545
        super(TestCaseWithMemoryTransport, self).__init__(methodName)
1415
 
        self.transport_server = default_transport
 
1546
        self.vfs_transport_factory = default_transport
 
1547
        self.transport_server = None
1416
1548
        self.transport_readonly_server = None
 
1549
        self.__vfs_server = None
1417
1550
 
1418
1551
    def get_transport(self):
1419
1552
        """Return a writeable transport for the test scratch space"""
1447
1580
            if self.transport_readonly_server is None:
1448
1581
                # readonly decorator requested
1449
1582
                # bring up the server
1450
 
                self.get_url()
1451
1583
                self.__readonly_server = ReadonlyServer()
1452
 
                self.__readonly_server.setUp(self.__server)
 
1584
                self.__readonly_server.setUp(self.get_vfs_only_server())
1453
1585
            else:
1454
1586
                self.__readonly_server = self.create_transport_readonly_server()
1455
 
                self.__readonly_server.setUp()
 
1587
                self.__readonly_server.setUp(self.get_vfs_only_server())
1456
1588
            self.addCleanup(self.__readonly_server.tearDown)
1457
1589
        return self.__readonly_server
1458
1590
 
1471
1603
            base = base + relpath
1472
1604
        return base
1473
1605
 
1474
 
    def get_server(self):
1475
 
        """Get the read/write server instance.
 
1606
    def get_vfs_only_server(self):
 
1607
        """Get the vfs only read/write server instance.
1476
1608
 
1477
1609
        This is useful for some tests with specific servers that need
1478
1610
        diagnostics.
1480
1612
        For TestCaseWithMemoryTransport this is always a MemoryServer, and there
1481
1613
        is no means to override it.
1482
1614
        """
 
1615
        if self.__vfs_server is None:
 
1616
            self.__vfs_server = MemoryServer()
 
1617
            self.__vfs_server.setUp()
 
1618
            self.addCleanup(self.__vfs_server.tearDown)
 
1619
        return self.__vfs_server
 
1620
 
 
1621
    def get_server(self):
 
1622
        """Get the read/write server instance.
 
1623
 
 
1624
        This is useful for some tests with specific servers that need
 
1625
        diagnostics.
 
1626
 
 
1627
        This is built from the self.transport_server factory. If that is None,
 
1628
        then the self.get_vfs_server is returned.
 
1629
        """
1483
1630
        if self.__server is None:
1484
 
            self.__server = MemoryServer()
1485
 
            self.__server.setUp()
 
1631
            if self.transport_server is None or self.transport_server is self.vfs_transport_factory:
 
1632
                return self.get_vfs_only_server()
 
1633
            else:
 
1634
                # bring up a decorated means of access to the vfs only server.
 
1635
                self.__server = self.transport_server()
 
1636
                try:
 
1637
                    self.__server.setUp(self.get_vfs_only_server())
 
1638
                except TypeError, e:
 
1639
                    # This should never happen; the try:Except here is to assist
 
1640
                    # developers having to update code rather than seeing an
 
1641
                    # uninformative TypeError.
 
1642
                    raise Exception, "Old server API in use: %s, %s" % (self.__server, e)
1486
1643
            self.addCleanup(self.__server.tearDown)
1487
1644
        return self.__server
1488
1645
 
1489
 
    def get_url(self, relpath=None):
 
1646
    def _adjust_url(self, base, relpath):
1490
1647
        """Get a URL (or maybe a path) for the readwrite transport.
1491
1648
 
1492
1649
        This will either be backed by '.' or to an equivalent non-file based
1494
1651
        relpath provides for clients to get a path relative to the base url.
1495
1652
        These should only be downwards relative, not upwards.
1496
1653
        """
1497
 
        base = self.get_server().get_url()
1498
1654
        if relpath is not None and relpath != '.':
1499
1655
            if not base.endswith('/'):
1500
1656
                base = base + '/'
1508
1664
                base += urlutils.escape(relpath)
1509
1665
        return base
1510
1666
 
 
1667
    def get_url(self, relpath=None):
 
1668
        """Get a URL (or maybe a path) for the readwrite transport.
 
1669
 
 
1670
        This will either be backed by '.' or to an equivalent non-file based
 
1671
        facility.
 
1672
        relpath provides for clients to get a path relative to the base url.
 
1673
        These should only be downwards relative, not upwards.
 
1674
        """
 
1675
        base = self.get_server().get_url()
 
1676
        return self._adjust_url(base, relpath)
 
1677
 
 
1678
    def get_vfs_only_url(self, relpath=None):
 
1679
        """Get a URL (or maybe a path for the plain old vfs transport.
 
1680
 
 
1681
        This will never be a smart protocol.
 
1682
        :param relpath: provides for clients to get a path relative to the base
 
1683
            url.  These should only be downwards relative, not upwards.
 
1684
        """
 
1685
        base = self.get_vfs_only_server().get_url()
 
1686
        return self._adjust_url(base, relpath)
 
1687
 
1511
1688
    def _make_test_root(self):
1512
1689
        if TestCaseWithMemoryTransport.TEST_ROOT is not None:
1513
1690
            return
1591
1768
        self.overrideEnvironmentForTesting()
1592
1769
        self.__readonly_server = None
1593
1770
        self.__server = None
 
1771
        self.reduceLockdirTimeout()
1594
1772
 
1595
1773
     
1596
1774
class TestCaseInTempDir(TestCaseWithMemoryTransport):
1735
1913
    readwrite one must both define get_url() as resolving to os.getcwd().
1736
1914
    """
1737
1915
 
1738
 
    def create_transport_server(self):
1739
 
        """Create a transport server from class defined at init.
1740
 
 
1741
 
        This is mostly a hook for daughter classes.
1742
 
        """
1743
 
        return self.transport_server()
1744
 
 
1745
 
    def get_server(self):
 
1916
    def get_vfs_only_server(self):
1746
1917
        """See TestCaseWithMemoryTransport.
1747
1918
 
1748
1919
        This is useful for some tests with specific servers that need
1749
1920
        diagnostics.
1750
1921
        """
1751
 
        if self.__server is None:
1752
 
            self.__server = self.create_transport_server()
1753
 
            self.__server.setUp()
1754
 
            self.addCleanup(self.__server.tearDown)
1755
 
        return self.__server
 
1922
        if self.__vfs_server is None:
 
1923
            self.__vfs_server = self.vfs_transport_factory()
 
1924
            self.__vfs_server.setUp()
 
1925
            self.addCleanup(self.__vfs_server.tearDown)
 
1926
        return self.__vfs_server
1756
1927
 
1757
1928
    def make_branch_and_tree(self, relpath, format=None):
1758
1929
        """Create a branch on the transport and a tree locally.
1759
1930
 
1760
1931
        If the transport is not a LocalTransport, the Tree can't be created on
1761
 
        the transport.  In that case the working tree is created in the local
1762
 
        directory, and the returned tree's branch and repository will also be
1763
 
        accessed locally.
1764
 
 
1765
 
        This will fail if the original default transport for this test
1766
 
        case wasn't backed by the working directory, as the branch won't
1767
 
        be on disk for us to open it.  
 
1932
        the transport.  In that case if the vfs_transport_factory is
 
1933
        LocalURLServer the working tree is created in the local
 
1934
        directory backing the transport, and the returned tree's branch and
 
1935
        repository will also be accessed locally. Otherwise a lightweight
 
1936
        checkout is created and returned.
1768
1937
 
1769
1938
        :param format: The BzrDirFormat.
1770
1939
        :returns: the WorkingTree.
1778
1947
            return b.bzrdir.create_workingtree()
1779
1948
        except errors.NotLocalUrl:
1780
1949
            # We can only make working trees locally at the moment.  If the
1781
 
            # transport can't support them, then reopen the branch on a local
1782
 
            # transport, and create the working tree there.  
1783
 
            #
1784
 
            # Possibly we should instead keep
1785
 
            # the non-disk-backed branch and create a local checkout?
1786
 
            bd = bzrdir.BzrDir.open(relpath)
1787
 
            return bd.create_workingtree()
 
1950
            # transport can't support them, then we keep the non-disk-backed
 
1951
            # branch and create a local checkout.
 
1952
            if self.vfs_transport_factory is LocalURLServer:
 
1953
                # the branch is colocated on disk, we cannot create a checkout.
 
1954
                # hopefully callers will expect this.
 
1955
                local_controldir= bzrdir.BzrDir.open(self.get_vfs_only_url(relpath))
 
1956
                return local_controldir.create_workingtree()
 
1957
            else:
 
1958
                return b.create_checkout(relpath, lightweight=True)
1788
1959
 
1789
1960
    def assertIsDirectory(self, relpath, transport):
1790
1961
        """Assert that relpath within transport is a directory.
1812
1983
 
1813
1984
    def setUp(self):
1814
1985
        super(TestCaseWithTransport, self).setUp()
1815
 
        self.__server = None
 
1986
        self.__vfs_server = None
1816
1987
 
1817
1988
 
1818
1989
class ChrootedTestCase(TestCaseWithTransport):
1829
2000
 
1830
2001
    def setUp(self):
1831
2002
        super(ChrootedTestCase, self).setUp()
1832
 
        if not self.transport_server == MemoryServer:
 
2003
        if not self.vfs_transport_factory == MemoryServer:
1833
2004
            self.transport_readonly_server = HttpServer
1834
2005
 
1835
2006
 
2002
2173
                   'bzrlib.tests.test_ssh_transport',
2003
2174
                   'bzrlib.tests.test_status',
2004
2175
                   'bzrlib.tests.test_store',
 
2176
                   'bzrlib.tests.test_strace',
2005
2177
                   'bzrlib.tests.test_subsume',
2006
2178
                   'bzrlib.tests.test_symbol_versioning',
2007
2179
                   'bzrlib.tests.test_tag',
2093
2265
            if not quiet:
2094
2266
                print 'delete directory:', i
2095
2267
            shutil.rmtree(i)
 
2268
 
 
2269
 
 
2270
class Feature(object):
 
2271
    """An operating system Feature."""
 
2272
 
 
2273
    def __init__(self):
 
2274
        self._available = None
 
2275
 
 
2276
    def available(self):
 
2277
        """Is the feature available?
 
2278
 
 
2279
        :return: True if the feature is available.
 
2280
        """
 
2281
        if self._available is None:
 
2282
            self._available = self._probe()
 
2283
        return self._available
 
2284
 
 
2285
    def _probe(self):
 
2286
        """Implement this method in concrete features.
 
2287
 
 
2288
        :return: True if the feature is available.
 
2289
        """
 
2290
        raise NotImplementedError
 
2291
 
 
2292
    def __str__(self):
 
2293
        if getattr(self, 'feature_name', None):
 
2294
            return self.feature_name()
 
2295
        return self.__class__.__name__