From 11c8af724d9369c92199870af2cc69704017d3a0 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Sun, 10 Jan 2016 21:00:39 -0800 Subject: [PATCH 01/17] Add more tests for dbcore --- test/test_dbcore.py | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/test_dbcore.py b/test/test_dbcore.py index a69ffedbe..204398eaa 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -116,6 +116,16 @@ class TestDatabaseTwoModels(dbcore.Database): pass +class TestModelWithGetters(dbcore.Model): + + @classmethod + def _getters(cls): + return {'aComputedField': (lambda s: 'thing')} + + def _template_funcs(self): + return {} + + class MigrationTest(unittest.TestCase): """Tests the ability to change the database schema between versions. @@ -274,6 +284,40 @@ class ModelTest(unittest.TestCase): model2.load() self.assertNotIn('flex_field', model2) + def test_check_db_fails(self): + with self.assertRaisesRegexp(ValueError, 'no database'): + dbcore.Model()._check_db() + with self.assertRaisesRegexp(ValueError, 'no id'): + TestModel1(self.db)._check_db() + + dbcore.Model(self.db)._check_db(need_id=False) + + def test_missing_field(self): + with self.assertRaises(AttributeError): + TestModel1(self.db).nonExistingKey + + def test_computed_field(self): + model = TestModelWithGetters() + self.assertEqual(model.aComputedField, 'thing') + with self.assertRaisesRegexp(KeyError, 'computed field .+ deleted'): + del model.aComputedField + + def test_items(self): + model = TestModel1(self.db) + model.id = 5 + self.assertEqual({('id', 5), ('field_one', None)}, + set(model.items())) + + def test_delete_internal_field(self): + model = dbcore.Model() + del model._db + with self.assertRaises(AttributeError): + model._db + + def test_parse_nonstring(self): + with self.assertRaisesRegexp(TypeError, "must be a string"): + dbcore.Model._parse(None, 42) + class FormatTest(unittest.TestCase): def test_format_fixed_field(self): @@ -588,6 +632,15 @@ class ResultsIteratorTest(unittest.TestCase): objs = self.db._fetch(TestModel1) self.assertEqual(len(objs), 2) + def test_out_of_range(self): + objs = self.db._fetch(TestModel1) + with self.assertRaises(IndexError): + objs[100] + + def test_no_results(self): + self.assertIsNone(self.db._fetch( + TestModel1, dbcore.query.FalseQuery()).get()) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From f770125e3cf3873f9812ab99a76a9f9fdd753186 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Sun, 10 Jan 2016 21:01:28 -0800 Subject: [PATCH 02/17] Avoid repeating expensive db creation Saves about 3 seconds --- test/test_dbcore.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 204398eaa..3c1a3599b 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -19,6 +19,7 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) import os +import shutil import sqlite3 from test._common import unittest @@ -130,11 +131,13 @@ class MigrationTest(unittest.TestCase): """Tests the ability to change the database schema between versions. """ - def setUp(self): - handle, self.libfile = mkstemp('db') + + @classmethod + def setUpClass(cls): + handle, cls.orig_libfile = mkstemp('orig_db') os.close(handle) # Set up a database with the two-field schema. - old_lib = TestDatabase2(self.libfile) + old_lib = TestDatabase2(cls.orig_libfile) # Add an item to the old library. old_lib._connection().execute( @@ -143,6 +146,15 @@ class MigrationTest(unittest.TestCase): old_lib._connection().commit() del old_lib + @classmethod + def tearDownClass(cls): + os.remove(cls.orig_libfile) + + def setUp(self): + handle, self.libfile = mkstemp('db') + os.close(handle) + shutil.copyfile(self.orig_libfile, self.libfile) + def tearDown(self): os.remove(self.libfile) From dda1173e02d73e5cfe07434238bd99dbd9e1ae45 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Sun, 10 Jan 2016 22:01:13 -0800 Subject: [PATCH 03/17] Escape quotes to make old version of emacs font-lock happy --- test/test_art.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_art.py b/test/test_art.py index e19c7d211..4954106b5 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -209,10 +209,10 @@ class AAOTest(UseThePlugin): def test_aao_scraper_finds_image(self): body = b"""
- - View larger image + + \"View """ self.mock_response(self.AAO_URL, body) album = _common.Bag(asin=self.ASIN) From 760298b8e464d0821d937cfdade895ed0a4c72d6 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Sun, 10 Jan 2016 23:18:47 -0800 Subject: [PATCH 04/17] Mark slow tests to be skipped if SKIP_SLOW_TESTS env var is defined Saves over 100 seconds. --- test/test_art.py | 2 ++ test/test_convert.py | 6 ++++++ test/test_dbcore.py | 2 ++ test/test_edit.py | 3 +++ test/test_importer.py | 2 ++ test/test_logging.py | 3 +++ test/test_ui.py | 6 ++++++ 7 files changed, 24 insertions(+) diff --git a/test/test_art.py b/test/test_art.py index 4954106b5..d4ca814a5 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -261,6 +261,8 @@ class GoogleImageTest(UseThePlugin): self.assertEqual(list(result_url), []) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class ArtImporterTest(UseThePlugin): def setUp(self): super(ArtImporterTest, self).setUp() diff --git a/test/test_convert.py b/test/test_convert.py index ab1fa2504..e6f75c0ad 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -64,6 +64,8 @@ class TestHelper(helper.TestHelper): .format(path, tag)) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class ImportConvertTest(unittest.TestCase, TestHelper): def setUp(self): @@ -99,6 +101,8 @@ class ImportConvertTest(unittest.TestCase, TestHelper): self.assertTrue(os.path.isfile(item.path)) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class ConvertCliTest(unittest.TestCase, TestHelper): def setUp(self): @@ -186,6 +190,8 @@ class ConvertCliTest(unittest.TestCase, TestHelper): self.assertFalse(os.path.exists(converted)) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class NeverConvertLossyFilesTest(unittest.TestCase, TestHelper): """Test the effect of the `never_convert_lossy_files` option. """ diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 3c1a3599b..00ef2f1fb 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -127,6 +127,8 @@ class TestModelWithGetters(dbcore.Model): return {} +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class MigrationTest(unittest.TestCase): """Tests the ability to change the database schema between versions. diff --git a/test/test_edit.py b/test/test_edit.py index 522437eaa..34bad25a7 100644 --- a/test/test_edit.py +++ b/test/test_edit.py @@ -15,6 +15,7 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) import codecs +import os from mock import patch from test._common import unittest @@ -62,6 +63,8 @@ class ModifyFileMocker(object): f.write(contents) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class EditCommandTest(unittest.TestCase, TestHelper): """ Black box tests for `beetsplug.edit`. Command line interaction is simulated using `test.helper.control_stdin()`, and yaml editing via an diff --git a/test/test_importer.py b/test/test_importer.py index b9f75f7ef..27dcd5a12 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -236,6 +236,8 @@ class ImportHelper(TestHelper): self.assertEqual(len(os.listdir(self.libdir)), 0) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class NonAutotaggedImportTest(_common.TestCase, ImportHelper): def setUp(self): self.setup_beets(disk=True) diff --git a/test/test_logging.py b/test/test_logging.py index 81df95a78..8517f0a3f 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -5,6 +5,7 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) import sys +import os import threading import logging as log from StringIO import StringIO @@ -163,6 +164,8 @@ class LoggingLevelTest(unittest.TestCase, helper.TestHelper): self.assertIn('dummy: debug import_stage', logs) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class ConcurrentEventsTest(TestCase, helper.TestHelper): """Similar to LoggingLevelTest but lower-level and focused on multiple events interaction. Since this is a bit heavy we don't do it in diff --git a/test/test_ui.py b/test/test_ui.py index b32a4c452..08097b3f2 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -594,6 +594,8 @@ class InputTest(_common.TestCase): self.assertEqual(album, u'\xc2me') +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class ConfigTest(unittest.TestCase, TestHelper): def setUp(self): self.setup_beets() @@ -1035,6 +1037,8 @@ class PathFormatTest(_common.TestCase): self.assertEqual(pf[1:], default_formats) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class PluginTest(_common.TestCase): def test_plugin_command_from_pluginpath(self): config['pluginpath'] = [os.path.join(_common.RSRC, 'beetsplug')] @@ -1042,6 +1046,8 @@ class PluginTest(_common.TestCase): ui._raw_main(['test']) +@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, + 'Skipping because test is slow') class CompletionTest(_common.TestCase): def test_completion(self): # Load plugin commands From 231981de0cb9ed9732aaf0ad084c30fa05a52d73 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 11 Jan 2016 10:13:26 -0800 Subject: [PATCH 05/17] Fix #1806: handle OSError in `stats -e` --- beets/ui/commands.py | 5 ++++- docs/changelog.rst | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 773c10cae..0022fac74 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1232,7 +1232,10 @@ def show_stats(lib, query, exact): for item in items: if exact: - total_size += os.path.getsize(item.path) + try: + total_size += os.path.getsize(item.path) + except OSError as exc: + log.info('could not get size of {}: {}', item.path, exc) else: total_size += int(item.length * item.bitrate / 8) total_time += item.length diff --git a/docs/changelog.rst b/docs/changelog.rst index 56810ef6c..808010aae 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -45,6 +45,8 @@ Fixes: * :doc:`/plugins/lyrics`: The Genius backend has been re-enabled. * :doc:`/plugins/edit`: Editing metadata now moves files, when appropriate (like the :ref:`modify-cmd` command). :bug:`1804` +* The :ref:`stats-cmd` command no longer crashes when files are missing or + inaccessible. :bug:`1806` .. _beets.io: http://beets.io/ .. _Beetbox: https://github.com/beetbox From 8cb545b779fe03217216ba5a71c898022cc8fed2 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 11 Jan 2016 10:30:33 -0800 Subject: [PATCH 06/17] Possibly fix #1805: bytes headers for requests --- beetsplug/fetchart.py | 4 ++-- docs/changelog.rst | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index fe32ab9ad..57d8e4c46 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -72,9 +72,9 @@ def _logged_get(log, *args, **kwargs): else: message = 'getting URL' - req = requests.Request('GET', *args, **req_kwargs) + req = requests.Request(b'GET', *args, **req_kwargs) with requests.Session() as s: - s.headers = {'User-Agent': 'beets'} + s.headers = {b'User-Agent': b'beets'} prepped = s.prepare_request(req) log.debug('{}: {}', message, prepped.url) return s.send(prepped, **send_kwargs) diff --git a/docs/changelog.rst b/docs/changelog.rst index 808010aae..2a27987f1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -47,6 +47,8 @@ Fixes: (like the :ref:`modify-cmd` command). :bug:`1804` * The :ref:`stats-cmd` command no longer crashes when files are missing or inaccessible. :bug:`1806` +* :doc:`/plugins/fetchart`: Possibly fix a Unicode-related crash when using + some versions of pyOpenSSL. :bug:`1805` .. _beets.io: http://beets.io/ .. _Beetbox: https://github.com/beetbox From d93f723263ec13ac4990707b467efbaae218af40 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Tue, 12 Jan 2016 21:53:52 -0800 Subject: [PATCH 07/17] Convert SKIP_SLOW_TESTS to its own decorator --- test/_common.py | 8 ++++++++ test/test_art.py | 3 +-- test/test_convert.py | 9 +++------ test/test_dbcore.py | 4 ++-- test/test_edit.py | 5 ++--- test/test_importer.py | 3 +-- test/test_logging.py | 5 ++--- test/test_ui.py | 9 +++------ 8 files changed, 22 insertions(+), 24 deletions(-) diff --git a/test/_common.py b/test/_common.py index 21c73858e..8105ef342 100644 --- a/test/_common.py +++ b/test/_common.py @@ -346,3 +346,11 @@ def system_mock(name): yield finally: platform.system = old_system + + +def slowTest(unused=None): + def _id(obj): + return obj + if 'SKIP_SLOW_TESTS' in os.environ: + return unittest.skip('test is slow') + return _id diff --git a/test/test_art.py b/test/test_art.py index d4ca814a5..b7b52c245 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -261,8 +261,7 @@ class GoogleImageTest(UseThePlugin): self.assertEqual(list(result_url), []) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class ArtImporterTest(UseThePlugin): def setUp(self): super(ArtImporterTest, self).setUp() diff --git a/test/test_convert.py b/test/test_convert.py index e6f75c0ad..8ba71bdc9 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -64,8 +64,7 @@ class TestHelper(helper.TestHelper): .format(path, tag)) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class ImportConvertTest(unittest.TestCase, TestHelper): def setUp(self): @@ -101,8 +100,7 @@ class ImportConvertTest(unittest.TestCase, TestHelper): self.assertTrue(os.path.isfile(item.path)) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class ConvertCliTest(unittest.TestCase, TestHelper): def setUp(self): @@ -190,8 +188,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper): self.assertFalse(os.path.exists(converted)) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class NeverConvertLossyFilesTest(unittest.TestCase, TestHelper): """Test the effect of the `never_convert_lossy_files` option. """ diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 00ef2f1fb..4b4cc18bb 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -22,6 +22,7 @@ import os import shutil import sqlite3 +from test import _common from test._common import unittest from beets import dbcore from tempfile import mkstemp @@ -127,8 +128,7 @@ class TestModelWithGetters(dbcore.Model): return {} -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class MigrationTest(unittest.TestCase): """Tests the ability to change the database schema between versions. diff --git a/test/test_edit.py b/test/test_edit.py index 34bad25a7..106cf1f25 100644 --- a/test/test_edit.py +++ b/test/test_edit.py @@ -15,9 +15,9 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) import codecs -import os from mock import patch +from test import _common from test._common import unittest from test.helper import TestHelper, control_stdin @@ -63,8 +63,7 @@ class ModifyFileMocker(object): f.write(contents) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class EditCommandTest(unittest.TestCase, TestHelper): """ Black box tests for `beetsplug.edit`. Command line interaction is simulated using `test.helper.control_stdin()`, and yaml editing via an diff --git a/test/test_importer.py b/test/test_importer.py index 27dcd5a12..fea4d78a0 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -236,8 +236,7 @@ class ImportHelper(TestHelper): self.assertEqual(len(os.listdir(self.libdir)), 0) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class NonAutotaggedImportTest(_common.TestCase, ImportHelper): def setUp(self): self.setup_beets(disk=True) diff --git a/test/test_logging.py b/test/test_logging.py index 8517f0a3f..478b7a7c6 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -5,7 +5,6 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) import sys -import os import threading import logging as log from StringIO import StringIO @@ -13,6 +12,7 @@ from StringIO import StringIO import beets.logging as blog from beets import plugins, ui import beetsplug +from test import _common from test._common import unittest, TestCase from test import helper @@ -164,8 +164,7 @@ class LoggingLevelTest(unittest.TestCase, helper.TestHelper): self.assertIn('dummy: debug import_stage', logs) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class ConcurrentEventsTest(TestCase, helper.TestHelper): """Similar to LoggingLevelTest but lower-level and focused on multiple events interaction. Since this is a bit heavy we don't do it in diff --git a/test/test_ui.py b/test/test_ui.py index 08097b3f2..207d39261 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -594,8 +594,7 @@ class InputTest(_common.TestCase): self.assertEqual(album, u'\xc2me') -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class ConfigTest(unittest.TestCase, TestHelper): def setUp(self): self.setup_beets() @@ -1037,8 +1036,7 @@ class PathFormatTest(_common.TestCase): self.assertEqual(pf[1:], default_formats) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class PluginTest(_common.TestCase): def test_plugin_command_from_pluginpath(self): config['pluginpath'] = [os.path.join(_common.RSRC, 'beetsplug')] @@ -1046,8 +1044,7 @@ class PluginTest(_common.TestCase): ui._raw_main(['test']) -@unittest.skipIf('SKIP_SLOW_TESTS' in os.environ, - 'Skipping because test is slow') +@_common.slowTest() class CompletionTest(_common.TestCase): def test_completion(self): # Load plugin commands From c292b77e9e46687ced85b8f75e75ebf530ff0547 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Tue, 12 Jan 2016 22:02:12 -0800 Subject: [PATCH 08/17] fix travis-ci flake8 warning --- beets/library.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beets/library.py b/beets/library.py index 59a20cabf..154b2be3c 100644 --- a/beets/library.py +++ b/beets/library.py @@ -86,8 +86,7 @@ class PathQuery(dbcore.FieldQuery): colon = query_part.find(':') if colon != -1: query_part = query_part[:colon] - return (os.sep in query_part - and os.path.exists(syspath(normpath(query_part)))) + return (os.sep in query_part and os.path.exists(syspath(normpath(query_part)))) def match(self, item): path = item.path if self.case_sensitive else item.path.lower() From ab2b1fa900fc8d756fa166a116a2cc504a5c3c25 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Tue, 12 Jan 2016 22:07:02 -0800 Subject: [PATCH 09/17] fix different travis-ci flake8 warning --- beets/library.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beets/library.py b/beets/library.py index 154b2be3c..1c2fac944 100644 --- a/beets/library.py +++ b/beets/library.py @@ -86,7 +86,8 @@ class PathQuery(dbcore.FieldQuery): colon = query_part.find(':') if colon != -1: query_part = query_part[:colon] - return (os.sep in query_part and os.path.exists(syspath(normpath(query_part)))) + return (os.sep in query_part and + os.path.exists(syspath(normpath(query_part)))) def match(self, item): path = item.path if self.case_sensitive else item.path.lower() From 72ce9ea3ebb45452605049ec24eecaa32c145e96 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Tue, 12 Jan 2016 22:14:12 -0800 Subject: [PATCH 10/17] use underscore in name for pep-8s sake --- test/_common.py | 2 +- test/test_art.py | 2 +- test/test_convert.py | 6 +++--- test/test_dbcore.py | 2 +- test/test_edit.py | 2 +- test/test_importer.py | 2 +- test/test_logging.py | 2 +- test/test_ui.py | 6 +++--- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/_common.py b/test/_common.py index 8105ef342..9f8b9f146 100644 --- a/test/_common.py +++ b/test/_common.py @@ -348,7 +348,7 @@ def system_mock(name): platform.system = old_system -def slowTest(unused=None): +def slow_test(unused=None): def _id(obj): return obj if 'SKIP_SLOW_TESTS' in os.environ: diff --git a/test/test_art.py b/test/test_art.py index b7b52c245..7902bb213 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -261,7 +261,7 @@ class GoogleImageTest(UseThePlugin): self.assertEqual(list(result_url), []) -@_common.slowTest() +@_common.slow_test() class ArtImporterTest(UseThePlugin): def setUp(self): super(ArtImporterTest, self).setUp() diff --git a/test/test_convert.py b/test/test_convert.py index 8ba71bdc9..72d52feaa 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -64,7 +64,7 @@ class TestHelper(helper.TestHelper): .format(path, tag)) -@_common.slowTest() +@_common.slow_test() class ImportConvertTest(unittest.TestCase, TestHelper): def setUp(self): @@ -100,7 +100,7 @@ class ImportConvertTest(unittest.TestCase, TestHelper): self.assertTrue(os.path.isfile(item.path)) -@_common.slowTest() +@_common.slow_test() class ConvertCliTest(unittest.TestCase, TestHelper): def setUp(self): @@ -188,7 +188,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper): self.assertFalse(os.path.exists(converted)) -@_common.slowTest() +@_common.slow_test() class NeverConvertLossyFilesTest(unittest.TestCase, TestHelper): """Test the effect of the `never_convert_lossy_files` option. """ diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 4b4cc18bb..39b7eea1e 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -128,7 +128,7 @@ class TestModelWithGetters(dbcore.Model): return {} -@_common.slowTest() +@_common.slow_test() class MigrationTest(unittest.TestCase): """Tests the ability to change the database schema between versions. diff --git a/test/test_edit.py b/test/test_edit.py index 106cf1f25..25b24cea0 100644 --- a/test/test_edit.py +++ b/test/test_edit.py @@ -63,7 +63,7 @@ class ModifyFileMocker(object): f.write(contents) -@_common.slowTest() +@_common.slow_test() class EditCommandTest(unittest.TestCase, TestHelper): """ Black box tests for `beetsplug.edit`. Command line interaction is simulated using `test.helper.control_stdin()`, and yaml editing via an diff --git a/test/test_importer.py b/test/test_importer.py index fea4d78a0..6eacab32d 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -236,7 +236,7 @@ class ImportHelper(TestHelper): self.assertEqual(len(os.listdir(self.libdir)), 0) -@_common.slowTest() +@_common.slow_test() class NonAutotaggedImportTest(_common.TestCase, ImportHelper): def setUp(self): self.setup_beets(disk=True) diff --git a/test/test_logging.py b/test/test_logging.py index 478b7a7c6..a3fe363b9 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -164,7 +164,7 @@ class LoggingLevelTest(unittest.TestCase, helper.TestHelper): self.assertIn('dummy: debug import_stage', logs) -@_common.slowTest() +@_common.slow_test() class ConcurrentEventsTest(TestCase, helper.TestHelper): """Similar to LoggingLevelTest but lower-level and focused on multiple events interaction. Since this is a bit heavy we don't do it in diff --git a/test/test_ui.py b/test/test_ui.py index 207d39261..ed1740643 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -594,7 +594,7 @@ class InputTest(_common.TestCase): self.assertEqual(album, u'\xc2me') -@_common.slowTest() +@_common.slow_test() class ConfigTest(unittest.TestCase, TestHelper): def setUp(self): self.setup_beets() @@ -1036,7 +1036,7 @@ class PathFormatTest(_common.TestCase): self.assertEqual(pf[1:], default_formats) -@_common.slowTest() +@_common.slow_test() class PluginTest(_common.TestCase): def test_plugin_command_from_pluginpath(self): config['pluginpath'] = [os.path.join(_common.RSRC, 'beetsplug')] @@ -1044,7 +1044,7 @@ class PluginTest(_common.TestCase): ui._raw_main(['test']) -@_common.slowTest() +@_common.slow_test() class CompletionTest(_common.TestCase): def test_completion(self): # Load plugin commands From 20e4f5d2098f2b76aaa1ec41e61e8f4e52da99d1 Mon Sep 17 00:00:00 2001 From: Guilherme Danno Date: Thu, 14 Jan 2016 18:08:22 -0200 Subject: [PATCH 11/17] Info plugin: add option to print only keys --- beetsplug/info.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/beetsplug/info.py b/beetsplug/info.py index 61f0c7971..e67527247 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -119,6 +119,24 @@ def print_data(data, item=None, fmt=None): ui.print_(lineformat.format(field, value)) +def print_data_keys(data, item=None): + + path = displayable_path(item.path) if item else None + formatted = [] + for key, value in data.iteritems(): + formatted.append(key) + + if len(formatted) == 0: + return + + line_format = u'{0}{{0}}'.format(u' ' * 4) + if path: + ui.print_(displayable_path(path)) + + for field in sorted(formatted): + ui.print_(line_format.format(field)) + + class InfoPlugin(BeetsPlugin): def commands(self): @@ -131,6 +149,8 @@ class InfoPlugin(BeetsPlugin): cmd.parser.add_option('-i', '--include-keys', default=[], action='append', dest='included_keys', help='comma separated list of keys to show') + cmd.parser.add_option('-k', '--keys-only', action='store_true', + help='show only the keys') cmd.parser.add_format_option(target='item') return [cmd] @@ -173,7 +193,10 @@ class InfoPlugin(BeetsPlugin): else: if not first: ui.print_() - print_data(data, item, opts.format) + if opts.keys_only: + print_data_keys(data, item) + else: + print_data(data, item, opts.format) first = False if opts.summarize: From f75b4b4e3b6b164234021381947b3b347a4152e1 Mon Sep 17 00:00:00 2001 From: Guilherme Danno Date: Fri, 15 Jan 2016 02:40:33 -0200 Subject: [PATCH 12/17] Update info docs --- docs/plugins/info.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/info.rst b/docs/plugins/info.rst index b36e69051..238a957ff 100644 --- a/docs/plugins/info.rst +++ b/docs/plugins/info.rst @@ -39,7 +39,7 @@ Additional command-line options include: * ``--format`` or ``-f``: Specify a specific format with which to print every item. This uses the same template syntax as beets’ :doc:`path formats `. - +* ``--keys-only`` or ``-k``: Show the name of the tags without the values. .. _id3v2: http://id3v2.sourceforge.net .. _mp3info: http://www.ibiblio.org/mp3info/ From 7251f2b9cdb5d54a7be3ced506965f4cddf21f42 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Fri, 15 Jan 2016 10:10:17 -0800 Subject: [PATCH 13/17] Changelog for #1812 --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2a27987f1..51fab3100 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -18,6 +18,8 @@ New: * A new :doc:`/plugins/mbsubmit` lets you print the tracks of an album in a format parseable by MusicBrainz track parser during an interactive import session. :bug:`1779` +* :doc:`/plugins/info`: A new option will print only fields' names and not + their values. Thanks to :user:`GuilhermeHideki`. :bug:`1812` .. _AcousticBrainz: http://acousticbrainz.org/ From 1866264732b85852db349a956dc871018ca00357 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Fri, 15 Jan 2016 10:10:56 -0800 Subject: [PATCH 14/17] Docsring for #1812 --- beetsplug/info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beetsplug/info.py b/beetsplug/info.py index e67527247..a29a6ccfc 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -120,7 +120,8 @@ def print_data(data, item=None, fmt=None): def print_data_keys(data, item=None): - + """Print only the keys (field names) for an item. + """ path = displayable_path(item.path) if item else None formatted = [] for key, value in data.iteritems(): From da1c4545aa5cd1ca3352a804456d7c2352458d25 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Sat, 16 Jan 2016 00:10:13 -0800 Subject: [PATCH 15/17] Add more test coverage for mpdstats --- test/test_mpdstats.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/test/test_mpdstats.py b/test/test_mpdstats.py index 9d7d16881..a2e2fb26a 100644 --- a/test/test_mpdstats.py +++ b/test/test_mpdstats.py @@ -17,7 +17,7 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) -from mock import Mock +from mock import Mock, patch, call, ANY from test._common import unittest from test.helper import TestHelper @@ -44,6 +44,44 @@ class MPDStatsTest(unittest.TestCase, TestHelper): self.assertFalse(mpdstats.update_rating(item, True)) self.assertFalse(mpdstats.update_rating(None, True)) + def test_get_item(self): + ITEM_PATH = '/foo/bar.flac' + item = Item(title='title', path=ITEM_PATH, id=1) + item.add(self.lib) + + log = Mock() + mpdstats = MPDStats(self.lib, log) + + self.assertEqual(str(mpdstats.get_item(ITEM_PATH)), str(item)) + self.assertIsNone(mpdstats.get_item('/some/non-existing/path')) + self.assertIn('item not found:', log.info.call_args[0][0]) + + FAKE_UNKNOWN_STATE = 'some-unknown-one' + STATUSES = [{'state': FAKE_UNKNOWN_STATE}, + {'state': 'pause'}, + {'state': 'play', 'songid': 1, 'time': '0:1'}, + {'state': 'stop'}] + ITEM_PATH = '/foo/bar.flac' + + @patch("beetsplug.mpdstats.MPDClientWrapper", return_value=Mock(**{ + "events.side_effect": [["player"]] * (len(STATUSES)-1) + [KeyboardInterrupt], + "status.side_effect": STATUSES, + "playlist.return_value": {1: ITEM_PATH}})) + def test_run_MPDStats(self, mpd_mock): + item = Item(title='title', path=self.ITEM_PATH, id=1) + item.add(self.lib) + + log = Mock() + try: + MPDStats(self.lib, log).run() + except KeyboardInterrupt: + pass + + log.debug.assert_has_calls( + [call(u'unhandled status "{0}"', ANY)]) + log.info.assert_has_calls( + [call(u'pause'), call(u'playing {0}', ANY), call(u'stop')]) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From e3d19b0a0f3120f122c28bfddfe02a2076715960 Mon Sep 17 00:00:00 2001 From: Jesse Weinstein Date: Sat, 16 Jan 2016 00:26:56 -0800 Subject: [PATCH 16/17] flake8 fixes --- test/test_mpdstats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_mpdstats.py b/test/test_mpdstats.py index a2e2fb26a..f28e29d68 100644 --- a/test/test_mpdstats.py +++ b/test/test_mpdstats.py @@ -61,11 +61,11 @@ class MPDStatsTest(unittest.TestCase, TestHelper): {'state': 'pause'}, {'state': 'play', 'songid': 1, 'time': '0:1'}, {'state': 'stop'}] + EVENTS = [["player"]] * (len(STATUSES) - 1) + [KeyboardInterrupt] ITEM_PATH = '/foo/bar.flac' @patch("beetsplug.mpdstats.MPDClientWrapper", return_value=Mock(**{ - "events.side_effect": [["player"]] * (len(STATUSES)-1) + [KeyboardInterrupt], - "status.side_effect": STATUSES, + "events.side_effect": EVENTS, "status.side_effect": STATUSES, "playlist.return_value": {1: ITEM_PATH}})) def test_run_MPDStats(self, mpd_mock): item = Item(title='title', path=self.ITEM_PATH, id=1) From 248b0b070c487c0ab406572293bb367a7f46da07 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 18 Jan 2016 16:08:54 -0500 Subject: [PATCH 17/17] Remove stray space in docs --- docs/plugins/ihate.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/ihate.rst b/docs/plugins/ihate.rst index f2224bf5a..f9cde39eb 100644 --- a/docs/plugins/ihate.rst +++ b/docs/plugins/ihate.rst @@ -25,7 +25,7 @@ Here's an example:: ihate: warn: - artist:rnb - - genre: soul + - genre:soul # Only warn about tribute albums in rock genre. - genre:rock album:tribute skip: