Commit f934acd8 authored by Ted Meyer's avatar Ted Meyer Committed by Commit Bot

MediaError class

MediaError is meant to be a relatively small (sizeof(void*) bytes)
object that can be returned as an error value from functions or passed
to callbacks that want a report of status. MediaError allows attaching
of arbitrary named data, other MediaErrors as causes, and stack frames,
which can all be logged and reported throughout the media stack. The
error code and message are immutable and can be used to give a stable
numeric ID for any error generated by media code. There is also an OK
state which can't hold any data and is only for successful returns.

doc: go/media-error

Bug: 1043289

Change-Id: I11a7c6d667f9810ba85264e41c8d7ce184858b34
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1867991Reviewed-by: default avatarFrank Liberato <liberato@chromium.org>
Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Commit-Queue: Ted Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734338}
parent e81ede34
...@@ -176,6 +176,9 @@ jumbo_source_set("base") { ...@@ -176,6 +176,9 @@ jumbo_source_set("base") {
"media_content_type.cc", "media_content_type.cc",
"media_content_type.h", "media_content_type.h",
"media_controller.h", "media_controller.h",
"media_error.cc",
"media_error.h",
"media_error_codes.h",
"media_export.h", "media_export.h",
"media_log.cc", "media_log.cc",
"media_log.h", "media_log.h",
...@@ -529,6 +532,7 @@ source_set("unit_tests") { ...@@ -529,6 +532,7 @@ source_set("unit_tests") {
"feedback_signal_accumulator_unittest.cc", "feedback_signal_accumulator_unittest.cc",
"frame_rate_estimator_unittest.cc", "frame_rate_estimator_unittest.cc",
"key_systems_unittest.cc", "key_systems_unittest.cc",
"media_error_unittest.cc",
"media_log_unittest.cc", "media_log_unittest.cc",
"media_serializers_unittest.cc", "media_serializers_unittest.cc",
"media_url_demuxer_unittest.cc", "media_url_demuxer_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/media_error.h"
#include <memory>
#include "media/base/media_serializers.h"
namespace media {
MediaError::MediaError(ErrorCode code,
base::StringPiece message,
const base::Location& location) {
if (code == ErrorCode::kOk) {
DCHECK(message.empty());
return;
}
data_ = std::make_unique<MediaErrorInternal>(code, message);
AddFrame(location);
}
// Copy Constructor
MediaError::MediaError(const MediaError& copy) {
if (copy.IsOk())
return;
data_ = std::make_unique<MediaErrorInternal>(copy.GetErrorCode(),
copy.GetErrorMessage());
for (const base::Value& frame : copy.data_->frames)
data_->frames.push_back(frame.Clone());
for (const MediaError& err : copy.data_->causes)
data_->causes.push_back(err);
data_->data = copy.data_->data.Clone();
}
// Allow move.
MediaError::MediaError(MediaError&&) = default;
MediaError& MediaError::operator=(MediaError&&) = default;
MediaError::~MediaError() = default;
MediaError::MediaErrorInternal::MediaErrorInternal(ErrorCode code,
base::StringPiece message)
: code(code),
message(message),
data(base::Value(base::Value::Type::DICTIONARY)) {}
MediaError::MediaErrorInternal::~MediaErrorInternal() = default;
MediaError&& MediaError::AddHere(const base::Location& location) && {
DCHECK(data_);
AddFrame(location);
return std::move(*this);
}
MediaError&& MediaError::AddCause(MediaError&& cause) && {
DCHECK(data_ && cause.data_);
data_->causes.push_back(std::move(cause));
return std::move(*this);
}
void MediaError::AddFrame(const base::Location& location) {
DCHECK(data_);
data_->frames.push_back(MediaSerialize(location));
}
} // namespace media
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_BASE_MEDIA_ERROR_H_
#define MEDIA_BASE_MEDIA_ERROR_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/location.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "media/base/media_error_codes.h"
#include "media/base/media_export.h"
#include "media/base/media_serializers_base.h"
namespace media {
// MediaError is meant to be a relatively small (sizeof(void*) bytes) object
// that can be returned as an error value from functions or passed to callbacks
// that want a report of status. MediaError allows attaching of arbitrary named
// data, other MediaErrors as causes, and stack frames, which can all be logged
// and reported throughout the media stack. The error code and message are
// immutable and can be used to give a stable numeric ID for any error
// generated by media code.
// There is also an OK state which can't hold any data and is only for
// successful returns.
class MEDIA_EXPORT MediaError {
public:
// Convenience function to return |kOk|.
// OK won't have a message, trace, or data associated with them, and DCHECK
// if they are added.
static MediaError Ok() { return MediaError(ErrorCode::kOk); }
// Constructor to create a new MediaError from a numeric code & message.
// These are immutable; if you'd like to change them, then you likely should
// create a new MediaError.
// NOTE: This should never be given a location parameter when called - It is
// defaulted in order to grab the caller location.
MediaError(ErrorCode code,
base::StringPiece message = "",
const base::Location& location = base::Location::Current());
// Copy Constructor
MediaError(const MediaError& copy);
// Allows move.
MediaError(MediaError&&);
MediaError& operator=(MediaError&&);
// Needs an out of line destructor...
~MediaError();
bool IsOk() const { return !data_; }
// Getters for internal fields
base::StringPiece GetErrorMessage() const {
return data_ ? data_->message : "";
}
ErrorCode GetErrorCode() const {
return data_ ? data_->code : ErrorCode::kOk;
}
// Adds the current location to MediaError as it’s passed upwards.
// This does not need to be called at every location that touches it, but
// should be called for those locations where the path is ambiguous or
// critical. This can be especially helpful across IPC boundaries. This will
// fail on an OK status.
// NOTE: This should never be given a parameter when called - It is defaulted
// in order to grab the caller location.
MediaError&& AddHere(
const base::Location& location = base::Location::Current()) &&;
// Add |cause| as the error that triggered this one. For example,
// DecoderStream might return kDecoderSelectionFailed with one or more causes
// that are the specific errors from the decoders that it tried.
MediaError&& AddCause(MediaError&& cause) &&;
void AddCause(MediaError&& cause) &;
// Allows us to append any datatype which can be converted to
// an int/bool/string/base::Value. Any existing data associated with |key|
// will be overwritten by |value|. This will fail on an OK status.
template <typename T>
MediaError&& WithData(const char* key, const T& value) && {
DCHECK(data_);
data_->data.SetKey(key, MediaSerialize(value));
return std::move(*this);
}
template <typename T>
void WithData(const char* key, const T& value) & {
DCHECK(data_);
data_->data.SetKey(key, MediaSerialize(value));
}
private:
// Private helper to add the current stack frame to the error trace.
void AddFrame(const base::Location& location);
// Keep the internal data in a unique ptr to minimize size of OK errors.
struct MediaErrorInternal {
MediaErrorInternal(ErrorCode code, base::StringPiece message);
~MediaErrorInternal();
// The current error code
ErrorCode code = ErrorCode::kOk;
// The current error message (Can be used for
// https://developer.mozilla.org/en-US/docs/Web/API/MediaError)
base::StringPiece message;
// Stack frames
std::vector<base::Value> frames;
// Causes
std::vector<MediaError> causes;
// Data attached to the error
base::Value data;
};
// Allow self-serialization
friend struct internal::MediaSerializer<MediaError>;
// A null internals is an implicit OK.
std::unique_ptr<MediaErrorInternal> data_;
};
// We need this two step macro to allow calling with no extra args - in a single
// step macro we would have no way of removing the trailing comma after the
// code.
#define MEDIA_ERROR(CODE_TRUNC, ...) \
MEDIA_ERROR_INTERNAL(::media::ErrorCode::CODE_TRUNC, ##__VA_ARGS__)
#define MEDIA_ERROR_INTERNAL(...) ::media::MediaError(__VA_ARGS__)
} // namespace media
#endif // MEDIA_BASE_MEDIA_ERROR_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_BASE_MEDIA_ERROR_CODES_H_
#define MEDIA_BASE_MEDIA_ERROR_CODES_H_
namespace media {
enum class ErrorCode : uint32_t {
kOk = 0,
kCodeOnlyForTesting = std::numeric_limits<uint32_t>::max(),
};
} // namespace media
#endif // MEDIA_BASE_MEDIA_ERROR_CODES_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sstream>
#include <string>
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "media/base/media_error.h"
#include "media/base/media_serializers.h"
#include "media/base/test_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::HasSubstr;
namespace media {
class UselessThingToBeSerialized {
public:
explicit UselessThingToBeSerialized(const char* name) : name_(name) {}
const char* name_;
};
namespace internal {
template <>
struct MediaSerializer<UselessThingToBeSerialized> {
static base::Value Serialize(const UselessThingToBeSerialized& t) {
return base::Value(t.name_);
}
};
} // namespace internal
// Friend class of MediaLog for access to internal constants.
class MediaErrorTest : public testing::Test {
public:
MediaError DontFail() { return MediaError::Ok(); }
MediaError FailEasily() {
return MEDIA_ERROR(kCodeOnlyForTesting, "Message");
}
MediaError FailRecursively(unsigned int count) {
if (!count) {
return FailEasily();
}
return FailRecursively(count - 1).AddHere();
}
template <typename T>
MediaError FailWithData(const char* key, const T& t) {
return MediaError(ErrorCode::kCodeOnlyForTesting, "Message", FROM_HERE)
.WithData(key, t);
}
MediaError FailWithCause() {
MediaError err = FailEasily();
return FailEasily().AddCause(std::move(err));
}
MediaError DoSomethingGiveItBack(MediaError me) {
me.WithData("data", "Hey you! psst! Help me outta here! I'm trapped!");
return me;
}
};
TEST_F(MediaErrorTest, StaticOKMethodGivesCorrectSerialization) {
MediaError ok = DontFail();
base::Value actual = MediaSerialize(ok);
ASSERT_EQ(actual.GetString(), "Ok");
}
TEST_F(MediaErrorTest, SingleLayerError) {
MediaError failed = FailEasily();
base::Value actual = MediaSerialize(failed);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 0ul);
const auto& stack = actual.FindListPath("stack")->GetList();
ASSERT_EQ(stack[0].DictSize(), 2ul); // line and file
// This is a bit fragile, since it's dependent on the file layout.
ASSERT_EQ(stack[0].FindIntPath("line").value_or(-1), 42);
ASSERT_THAT(*stack[0].FindStringPath("file"),
HasSubstr("media_error_unittest.cc"));
}
TEST_F(MediaErrorTest, MultipleErrorLayer) {
MediaError failed = FailRecursively(3);
base::Value actual = MediaSerialize(failed);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 4ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 0ul);
const auto& stack = actual.FindListPath("stack")->GetList();
ASSERT_EQ(stack[0].DictSize(), 2ul); // line and file
}
TEST_F(MediaErrorTest, CanHaveData) {
MediaError failed = FailWithData("example", "data");
base::Value actual = MediaSerialize(failed);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 1ul);
const auto& stack = actual.FindListPath("stack")->GetList();
ASSERT_EQ(stack[0].DictSize(), 2ul); // line and file
ASSERT_EQ(*actual.FindDictPath("data")->FindStringPath("example"), "data");
}
TEST_F(MediaErrorTest, CanUseCustomSerializer) {
MediaError failed = FailWithData("example", UselessThingToBeSerialized("F"));
base::Value actual = MediaSerialize(failed);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 1ul);
const auto& stack = actual.FindListPath("stack")->GetList();
ASSERT_EQ(stack[0].DictSize(), 2ul); // line and file
ASSERT_EQ(*actual.FindDictPath("data")->FindStringPath("example"), "F");
}
TEST_F(MediaErrorTest, CausedByHasVector) {
MediaError causal = FailWithCause();
base::Value actual = MediaSerialize(causal);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 0ul);
base::Value& nested = actual.FindListPath("causes")->GetList()[0];
ASSERT_EQ(nested.DictSize(), 5ul);
ASSERT_EQ(nested.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*nested.FindStringPath("error_message"), "Message");
ASSERT_EQ(nested.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(nested.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(nested.FindDictPath("data")->DictSize(), 0ul);
}
TEST_F(MediaErrorTest, CanCopyEasily) {
MediaError failed = FailEasily();
MediaError withData = DoSomethingGiveItBack(failed);
base::Value actual = MediaSerialize(failed);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 0ul);
actual = MediaSerialize(withData);
ASSERT_EQ(actual.DictSize(), 5ul);
ASSERT_EQ(actual.FindIntPath("error_code"),
static_cast<int>(ErrorCode::kCodeOnlyForTesting));
ASSERT_EQ(*actual.FindStringPath("error_message"), "Message");
ASSERT_EQ(actual.FindListPath("stack")->GetList().size(), 1ul);
ASSERT_EQ(actual.FindListPath("causes")->GetList().size(), 0ul);
ASSERT_EQ(actual.FindDictPath("data")->DictSize(), 1ul);
}
} // namespace media
...@@ -7,9 +7,12 @@ ...@@ -7,9 +7,12 @@
#include <vector> #include <vector>
#include "base/location.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "media/base/audio_decoder_config.h" #include "media/base/audio_decoder_config.h"
#include "media/base/buffering_state.h" #include "media/base/buffering_state.h"
#include "media/base/media_error.h"
#include "media/base/media_error_codes.h"
#include "media/base/media_serializers_base.h" #include "media/base/media_serializers_base.h"
#include "media/base/video_decoder_config.h" #include "media/base/video_decoder_config.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
...@@ -344,6 +347,42 @@ struct MediaSerializer<media::SerializableBufferingState<T>> { ...@@ -344,6 +347,42 @@ struct MediaSerializer<media::SerializableBufferingState<T>> {
} }
}; };
// enum (simple)
template <>
struct MediaSerializer<media::ErrorCode> {
static inline base::Value Serialize(media::ErrorCode code) {
return base::Value(static_cast<int>(code));
}
};
// Class (complex)
template <>
struct MediaSerializer<media::MediaError> {
static base::Value Serialize(const media::MediaError& err) {
if (err.IsOk())
return base::Value("Ok");
base::Value result(base::Value::Type::DICTIONARY);
FIELD_SERIALIZE("error_code", err.GetErrorCode());
FIELD_SERIALIZE("error_message", err.GetErrorMessage());
FIELD_SERIALIZE("stack", err.data_->frames);
FIELD_SERIALIZE("data", err.data_->data);
FIELD_SERIALIZE("causes", err.data_->causes);
return result;
}
};
// Class (complex)
template <>
struct MediaSerializer<base::Location> {
static base::Value Serialize(const base::Location& value) {
base::Value result(base::Value::Type::DICTIONARY);
FIELD_SERIALIZE("file", value.file_name());
FIELD_SERIALIZE("line", value.line_number());
return result;
}
};
#undef FIELD_SERIALIZE #undef FIELD_SERIALIZE
} // namespace internal } // namespace internal
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment