From 9ca237bfa2374ecc2eab86ffe11cdc61d667b937 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Fri, 28 Jan 2011 20:52:38 +0000 Subject: [PATCH] Add a unit test for some basic python scripting functionality --- src/scripting/python/pythonengine.cpp | 21 +++-- src/scripting/scriptmanager.cpp | 2 + src/scripting/scriptmanager.h | 8 +- tests/CMakeLists.txt | 1 + tests/python_test.cpp | 128 ++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 tests/python_test.cpp diff --git a/src/scripting/python/pythonengine.cpp b/src/scripting/python/pythonengine.cpp index c1ea99f96..c795588b6 100644 --- a/src/scripting/python/pythonengine.cpp +++ b/src/scripting/python/pythonengine.cpp @@ -47,6 +47,10 @@ PythonEngine::PythonEngine(ScriptManager* manager) PythonEngine::~PythonEngine() { sInstance = NULL; + + if (initialised_) { + Py_Finalize(); + } } const sipAPIDef* PythonEngine::GetSIPApi() { @@ -107,14 +111,17 @@ Script* PythonEngine::CreateScript(const ScriptInfo& info) { sip_api_ = GetSIPApi(); // Add objects to the module - AddObject(manager()->data().library_->backend(), sipType_LibraryBackend, "library"); - AddObject(manager()->data().player_, sipType_Player, "player"); - AddObject(manager()->data().playlists_, sipType_PlaylistManager, "playlists"); - AddObject(manager()->data().task_manager_, sipType_TaskManager, "task_manager"); - AddObject(manager()->data().settings_dialog_, sipType_SettingsDialog, "settings_dialog"); - AddObject(manager()->data().radio_model_, sipType_RadioModel, "radio_model"); + if (manager()->data().valid_) { + AddObject(manager()->data().library_->backend(), sipType_LibraryBackend, "library"); + AddObject(manager()->data().library_view_, sipType_LibraryView, "library_view"); + AddObject(manager()->data().player_, sipType_Player, "player"); + AddObject(manager()->data().playlists_, sipType_PlaylistManager, "playlists"); + AddObject(manager()->data().radio_model_, sipType_RadioModel, "radio_model"); + AddObject(manager()->data().settings_dialog_, sipType_SettingsDialog, "settings_dialog"); + AddObject(manager()->data().task_manager_, sipType_TaskManager, "task_manager"); + } + AddObject(manager()->ui(), sipType_UIInterface, "ui"); - AddObject(manager()->data().library_view_, sipType_LibraryView, "library_view"); AddObject(this, sipType_PythonEngine, "pythonengine"); // Create a module for scripts diff --git a/src/scripting/scriptmanager.cpp b/src/scripting/scriptmanager.cpp index 735e635db..0e6f35f9d 100644 --- a/src/scripting/scriptmanager.cpp +++ b/src/scripting/scriptmanager.cpp @@ -86,6 +86,7 @@ ScriptManager::~ScriptManager() { info.loaded()->language()->DestroyScript(info.loaded()); } } + qDeleteAll(engines_); } void ScriptManager::Init(const GlobalData& data) { @@ -304,6 +305,7 @@ void ScriptManager::AddLogLine(const QString& who, const QString& message, bool } log_lines_ << html; + log_lines_plain_ << plain; emit LogLineAdded(html); qDebug() << plain.toLocal8Bit().constData(); diff --git a/src/scripting/scriptmanager.h b/src/scripting/scriptmanager.h index b834f92ea..1fff7fe66 100644 --- a/src/scripting/scriptmanager.h +++ b/src/scripting/scriptmanager.h @@ -57,11 +57,12 @@ public: }; struct GlobalData { - GlobalData() {} + GlobalData() : valid_(false) {} GlobalData(Library* library, LibraryView* library_view, Player* player, PlaylistManager* playlists, TaskManager* task_manager, SettingsDialog* settings_dialog, RadioModel* radio_model) - : library_(library), + : valid_(true), + library_(library), library_view_(library_view), player_(player), playlists_(playlists), @@ -70,6 +71,7 @@ public: radio_model_(radio_model) {} + bool valid_; Library* library_; LibraryView* library_view_; Player* player_; @@ -90,6 +92,7 @@ public: void ShowSettingsDialog(const QModelIndex& index); QStringList log_lines() const { return log_lines_; } + QStringList log_lines_plain() const { return log_lines_plain_; } // QAbstractListModel int rowCount(const QModelIndex& parent = QModelIndex()) const; @@ -130,6 +133,7 @@ private: // HTML log messages QStringList log_lines_; + QStringList log_lines_plain_; // Things available to scripts GlobalData data_; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 20f02443c..c395a6606 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -109,6 +109,7 @@ add_test_file(mergedproxymodel_test.cpp false) add_test_file(organiseformat_test.cpp false) add_test_file(playlist_test.cpp true) add_test_file(plsparser_test.cpp false) +add_test_file(python_test.cpp true) add_test_file(scopedtransaction_test.cpp false) add_test_file(songloader_test.cpp false) add_test_file(songplaylistitem_test.cpp false) diff --git a/tests/python_test.cpp b/tests/python_test.cpp new file mode 100644 index 000000000..a7301cecd --- /dev/null +++ b/tests/python_test.cpp @@ -0,0 +1,128 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "core/utilities.h" +#include "scripting/script.h" +#include "scripting/scriptmanager.h" +#include "scripting/python/pythonengine.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils.h" + +#include +#include + +#include +#include + +namespace { + +class TemporaryScript : boost::noncopyable { +public: + TemporaryScript(const char* code) { + directory_ = Utilities::MakeTempDir(); + + QSettings ini(directory_ + "/script.ini", QSettings::IniFormat); + ini.beginGroup("Script"); + ini.setValue("language", "python"); + ini.setValue("script_file", "script.py"); + + QFile script(directory_ + "/script.py"); + script.open(QIODevice::WriteOnly); + script.write(code); + } + + ~TemporaryScript() { + if (!directory_.isEmpty()) { + Utilities::RemoveRecursive(directory_); + } + } + + QString directory_; +}; + + +class PythonTest : public ::testing::Test { + protected: + void SetUp() { + manager_ = new ScriptManager; + engine_ = qobject_cast( + manager_->EngineForLanguage(ScriptInfo::Language_Python)); + } + + void TearDown() { + delete manager_; + } + + ScriptManager* manager_; + PythonEngine* engine_; +}; + + +TEST_F(PythonTest, HasPythonEngine) { + ASSERT_TRUE(engine_); +} + +TEST_F(PythonTest, InitFromDirectory) { + TemporaryScript script("pass"); + + ScriptInfo info; + info.InitFromDirectory(manager_, script.directory_); + + EXPECT_TRUE(info.is_valid()); + EXPECT_EQ(script.directory_, info.path()); + EXPECT_EQ(ScriptInfo::Language_Python, info.language()); + EXPECT_EQ(NULL, info.loaded()); +} + +TEST_F(PythonTest, StdioIsRedirected) { + TemporaryScript script( + "import sys\n" + "print 'text on stdout'\n" + "print >>sys.stderr, 'text on stderr'\n"); + ScriptInfo info; + info.InitFromDirectory(manager_, script.directory_); + + engine_->CreateScript(info); + + QString log = manager_->log_lines_plain().join("\n"); + ASSERT_TRUE(log.contains("text on stdout")); + ASSERT_TRUE(log.contains("text on stderr")); +} + +TEST_F(PythonTest, CleanupModuleDict) { + TemporaryScript script( + "class Foo:\n" + " def __init__(self):\n" + " print 'constructor'\n" + " def __del__(self):\n" + " print 'destructor'\n" + "f = Foo()\n"); + + ScriptInfo info; + info.InitFromDirectory(manager_, script.directory_); + + Script* s = engine_->CreateScript(info); + ASSERT_TRUE(manager_->log_lines().last().endsWith("constructor")); + + engine_->DestroyScript(s); + ASSERT_TRUE(manager_->log_lines().last().endsWith("destructor")); +} + +} // namespace