Add mostly done M3UParser with basic unit tests.

Add lots of test infrastructure.
This commit is contained in:
John Maguire 2010-03-01 15:40:12 +00:00
parent 7e500a2fa8
commit c043eaba0c
8 changed files with 218 additions and 15 deletions

View File

@ -53,6 +53,7 @@ set(CLEMENTINE-SOURCES
shortcutsdialog.cpp
albumcovermanager.cpp
albumcoverloader.cpp
m3uparser.cpp
)
# Header files that have Q_OBJECT in
@ -99,6 +100,7 @@ set(CLEMENTINE-MOC-HEADERS
shortcutsdialog.h
albumcovermanager.h
albumcoverloader.h
m3uparser.h
)
# UI files
@ -148,6 +150,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/qtsingleapplication")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/qxt")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/gtest/include")
set(EXECUTABLE_OUTPUT_PATH ..)
add_library(clementine_lib

91
src/m3uparser.cpp Normal file
View File

@ -0,0 +1,91 @@
#include "m3uparser.h"
#include <QtDebug>
M3UParser::M3UParser(QIODevice* device, QDir directory, QObject* parent)
: QObject(parent),
device_(device),
type_(STANDARD),
directory_(directory) {
}
const QList<Song>& M3UParser::Parse() {
QString line = QString::fromLatin1(device_->readLine());
if (line == "#EXTM3U") {
// This is in extended M3U format.
type_ = EXTENDED;
line = QString::fromLatin1(device_->readLine());
}
do {
if (line.startsWith('#')) {
// Extended info or comment.
if (type_ == EXTENDED && line.startsWith("#EXT")) {
if (!ParseMetadata(line, &current_metadata_)) {
qWarning() << "Failed to parse metadata: " << line;
}
}
} else {
// Track location.
QUrl url;
if (!ParseTrackLocation(line, &url)) {
qWarning() << "Failed to parse location: " << line;
}
}
line = QString::fromLatin1(device_->readLine());
} while (device_->canReadLine());
return songs_;
}
bool M3UParser::ParseMetadata(QString line, M3UParser::Metadata* metadata) const {
// Extended info, eg.
// #EXTINF:123,Sample Artist - Sample title
QString info = line.section(':', 1);
QString l = info.section(',', 0, 0);
bool ok = false;
int length = l.toInt(&ok);
if (!ok) {
return false;
}
QString track_info = info.section(',', 1);
QStringList list = track_info.split('-');
if (list.size() <= 1) {
return false;
}
metadata->artist = list[0].trimmed();
metadata->title = list[1].trimmed();
metadata->length = length;
return true;
}
bool M3UParser::ParseTrackLocation(QString line, QUrl* url) const {
if (line.contains(QRegExp("^[a-z]+://"))) {
// Looks like a url.
QUrl temp(line);
if (temp.isValid()) {
*url = temp;
return true;
} else {
return false;
}
}
// Should be a local path.
if (line.contains(QRegExp("^([a-zA-Z]:\\)|(/)"))) {
// Absolute path.
// Fix windows \.
QString proper_path = line.replace('\\', '/');
if (!proper_path.startsWith('/')) {
proper_path.prepend("/");
}
*url = "file://" + proper_path;
return true;
} else {
// Relative path.
QString proper_path = line.replace('\\', '/');
QString absolute_path = directory_.absoluteFilePath(proper_path);
*url = "file://" + absolute_path;
return true;
}
}

49
src/m3uparser.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef M3UPARSER_H
#define M3UPARSER_H
#include <QDir>
#include <QObject>
#include <QUrl>
#include "gtest/gtest_prod.h"
#include "song.h"
class QIODevice;
class M3UParser : public QObject {
Q_OBJECT
public:
M3UParser(QIODevice* device, QDir directory = QDir(), QObject* parent = 0);
virtual ~M3UParser() {}
const QList<Song>& Parse();
struct Metadata {
QString artist;
QString title;
int length;
};
private:
enum M3UType {
STANDARD = 0,
EXTENDED, // Includes extended info (track, artist, etc.)
LINK // Points to a directory.
};
bool ParseMetadata(QString line, Metadata* metadata) const;
bool ParseTrackLocation(QString line, QUrl* url) const;
FRIEND_TEST(M3UParserTest, ParsesMetadata);
FRIEND_TEST(M3UParserTest, ParsesTrackLocation);
QIODevice* device_;
M3UType type_;
QDir directory_;
Metadata current_metadata_;
QList<Song> songs_;
};
#endif // M3UPARSER_H

View File

@ -12,20 +12,27 @@ set(GTEST-SOURCES
../3rdparty/gtest/src/gtest-port.cc
../3rdparty/gtest/src/gtest-test-part.cc
../3rdparty/gtest/src/gtest-typed-test.cc
test_utils.cpp
)
add_library(gtest ${GTEST-SOURCES})
set(SONGTEST-SOURCES
../3rdparty/gtest/src/gtest_main.cc
../src/song.cpp
song_test.cpp)
add_executable(song_test ${SONGTEST-SOURCES})
target_link_libraries(song_test clementine_lib gtest)
add_custom_target(test
./song_test
echo "Running tests"
WORKING_DIRECTORY ${CURRENT_BINARY_DIR}
DEPENDS song_test)
DEPENDS song_test m3uparser_test)
# Given a file foo_test.cpp, creates a target foo_test and adds it to the test target.
macro(add_test_file test_source)
get_filename_component(TEST_NAME ${ARGV0} NAME_WE)
add_executable(${TEST_NAME}
${ARGV0}
../3rdparty/gtest/src/gtest_main.cc)
target_link_libraries(${TEST_NAME} clementine_lib gtest)
add_custom_command(TARGET test POST_BUILD
COMMAND ./${TEST_NAME})
endmacro (add_test_file)
add_test_file(m3uparser_test.cpp)
add_test_file(song_test.cpp)

29
tests/m3uparser_test.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "gtest/gtest.h"
#include "test_utils.h"
#include "m3uparser.h"
class M3UParserTest : public ::testing::Test {
protected:
M3UParserTest()
: parser_(NULL) {
}
M3UParser parser_;
};
TEST_F(M3UParserTest, ParsesMetadata) {
QString line("#EXTINF:123,Foo artist - Foo track");
M3UParser::Metadata metadata;
ASSERT_TRUE(parser_.ParseMetadata(line, &metadata));
EXPECT_EQ("Foo artist", metadata.artist.toStdString());
EXPECT_EQ("Foo track", metadata.title.toStdString());
EXPECT_EQ(123, metadata.length);
}
TEST_F(M3UParserTest, ParsesTrackLocation) {
QString line("/foo/bar.mp3");
QUrl url;
ASSERT_TRUE(parser_.ParseTrackLocation(line, &url));
EXPECT_EQ(QUrl("file:///foo/bar.mp3"), url);
}

View File

@ -3,10 +3,7 @@
#include "gtest/gtest.h"
std::ostream& operator<<(std::ostream& stream, const QString& str) {
stream << str.toStdString();
return stream;
}
#include "test_utils.h"
namespace {

15
tests/test_utils.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "test_utils.h"
#include <QString>
#include <QUrl>
std::ostream& operator<<(std::ostream& stream, const QString& str) {
stream << str.toStdString();
return stream;
}
std::ostream& operator <<(std::ostream& stream, const QUrl& url) {
stream << url.toString().toStdString();
return stream;
}

12
tests/test_utils.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef TEST_UTILS_H
#define TEST_UTILS_H
#include <iostream>
class QString;
class QUrl;
std::ostream& operator <<(std::ostream& stream, const QString& str);
std::ostream& operator <<(std::ostream& stream, const QUrl& url);
#endif // TEST_UTILS_H