blob: 7f4c60fa782b8129ed11293aba5844bd9ae0aa23 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/hls/items.h"
#include "base/location.h"
#include "base/strings/string_piece.h"
#include "media/formats/hls/source_string.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
namespace media::hls {
namespace {
using LineResult = GetNextLineItemResult;
void CheckSourceString(SourceString expected,
SourceString actual,
const base::Location& from) {
EXPECT_EQ(expected.Line(), actual.Line()) << from.ToString();
EXPECT_EQ(expected.Column(), actual.Column()) << from.ToString();
EXPECT_EQ(expected.Str(), actual.Str()) << from.ToString();
}
// Calls `GetNextLineItem` for each expectation, and verifies that the result
// matches.
template <typename T>
void RunTest(base::StringPiece source,
const T& expectations,
const base::Location& from = base::Location::Current()) {
auto line_iter = SourceLineIterator(source);
for (auto expectation : expectations) {
auto result = GetNextLineItem(&line_iter);
if (expectation.has_value()) {
auto expected_value = std::move(expectation).value();
EXPECT_TRUE(result.has_value()) << from.ToString();
auto value = std::move(result).value();
// Ensure that resulting variants are the same
static_assert(absl::variant_size<LineResult>::value == 2, "");
if (auto* expected_tag = absl::get_if<TagItem>(&expected_value)) {
auto tag = absl::get<TagItem>(std::move(value));
EXPECT_EQ(expected_tag->GetName(), tag.GetName()) << from.ToString();
EXPECT_EQ(expected_tag->GetLineNumber(), tag.GetLineNumber())
<< from.ToString();
EXPECT_EQ(expected_tag->GetContent().has_value(),
tag.GetContent().has_value())
<< from.ToString();
if (expected_tag->GetContent().has_value() &&
tag.GetContent().has_value()) {
CheckSourceString(*expected_tag->GetContent(), *tag.GetContent(),
from);
}
} else {
auto expected_uri = absl::get<UriItem>(std::move(expected_value));
auto uri = absl::get<UriItem>(std::move(value));
CheckSourceString(expected_uri.content, uri.content, from);
}
} else {
EXPECT_FALSE(result.has_value()) << from.ToString();
auto error = std::move(result).error();
auto expected_error = std::move(expectation).error();
EXPECT_EQ(error.code(), expected_error.code()) << from.ToString();
}
}
}
template <typename T>
ParseStatus::Or<LineResult> ExpectTag(T name,
size_t line,
size_t col,
base::StringPiece content) {
return LineResult(TagItem::Create(
ToTagName(name), SourceString::CreateForTesting(line, col, content)));
}
template <typename T>
ParseStatus::Or<LineResult> ExpectEmptyTag(T name, size_t line) {
return LineResult(TagItem::CreateEmpty(ToTagName(name), line));
}
ParseStatus::Or<LineResult> ExpectUnknownTag(base::StringPiece name,
size_t line) {
return LineResult(
TagItem::CreateUnknown(SourceString::CreateForTesting(line, 2, name)));
}
ParseStatus::Or<LineResult> ExpectUri(size_t line,
size_t col,
base::StringPiece content) {
return LineResult(
UriItem{.content = SourceString::CreateForTesting(line, col, content)});
}
} // namespace
TEST(HlsItemsTest, GetNextLineItem1) {
constexpr base::StringPiece kManifest =
"#EXTM3U\n"
"\n"
"#ExTm3U\n"
"# EXTM3U\n"
"#EXTasdf\n"
"##Comment\n"
"EXTM3U\n"
"\r\n"
"# Comment\n"
"http://www.example.com\n"
"../media.m3u8\n"
"foobar.jpg\n"
" video about food.mov\r\n"
"uri_with_{$variable}.mov\r\n"
"#EXT-X-VERSION:7\n"
"#EXT-X-VERSION:\n"
"#EXT-X-VERSION\n"
"#EXT-X-VERSION-FOO\n"
"#EXTINF:1234,\t\n";
const ParseStatus::Or<LineResult> kExpectations[] = {
ExpectEmptyTag(CommonTagName::kM3u, 1), ExpectUnknownTag("EXTasdf", 5),
// Lines without leading # should be considered URIs
ExpectUri(7, 1, "EXTM3U"), ExpectUri(10, 1, "http://www.example.com"),
ExpectUri(11, 1, "../media.m3u8"), ExpectUri(12, 1, "foobar.jpg"),
// Whitespace is not allowed here, but that's not this function's
// responsibility.
ExpectUri(13, 1, " video about food.mov"),
// Variable substitution is not this function's responsibility.
ExpectUri(14, 1, "uri_with_{$variable}.mov"),
ExpectTag(CommonTagName::kXVersion, 15, 16, "7"),
ExpectTag(CommonTagName::kXVersion, 16, 16, ""),
ExpectEmptyTag(CommonTagName::kXVersion, 17),
ExpectUnknownTag("EXT-X-VERSION-FOO", 18),
ExpectTag(MediaPlaylistTagName::kInf, 19, 9, "1234,\t"),
ParseStatusCode::kReachedEOF, ParseStatusCode::kReachedEOF};
RunTest(kManifest, kExpectations);
}
TEST(HlsItemsTest, GetNextLineItem2) {
constexpr base::StringPiece kManifest =
"#EXTM3U\n"
"https://ww\rw.example.com\n"
"#EXT-X-VERSION:3\n";
const ParseStatus::Or<LineResult> kExpectations[] = {
ExpectEmptyTag(CommonTagName::kM3u, 1), ParseStatusCode::kInvalidEOL,
ParseStatusCode::kInvalidEOL};
RunTest(kManifest, kExpectations);
}
TEST(HlsItemsTest, GetNextLineItem3) {
constexpr base::StringPiece kManifest = "#EXTM3U";
const ParseStatus::Or<LineResult> kExpectations[] = {
ParseStatusCode::kInvalidEOL, ParseStatusCode::kInvalidEOL};
RunTest(kManifest, kExpectations);
}
TEST(HlsItemsTest, GetNextLineItem4) {
constexpr base::StringPiece kManifest = "#EXTM3U\r";
const ParseStatus::Or<LineResult> kExpectations[] = {
ParseStatusCode::kInvalidEOL, ParseStatusCode::kInvalidEOL};
RunTest(kManifest, kExpectations);
}
TEST(HlsItemsTest, GetNextLineItem5) {
constexpr base::StringPiece kManifest = "\n";
const ParseStatus::Or<LineResult> kExpectations[] = {
ParseStatusCode::kReachedEOF, ParseStatusCode::kReachedEOF};
RunTest(kManifest, kExpectations);
}
} // namespace media::hls