Commit 477a9e34 authored by huangs's avatar huangs Committed by Commit Bot

[Zucchini] Add io_utils.

This CL adds Zucchini utilities related to parsing and printing, along
with tests. Details:
- LimitedOutputStream: Used for printing error messages without
  flooding console with output.
- AsHex: Syntactical sugar for iostream-printing of hex values.
- PrefixSep: Syntactical sugar for iostream-printing of list of values.
- EatChar and StrictUInt: Parse command line parameters. Will be used to
  parse imposed matches.

Bug: 729154
Change-Id: I232295419172a5b52cbb39cea89009a2e03fb684
Reviewed-on: https://chromium-review.googlesource.com/567265
Commit-Queue: Erik Chen <erikchen@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486019}
parent e1071718
...@@ -13,6 +13,8 @@ static_library("zucchini_lib") { ...@@ -13,6 +13,8 @@ static_library("zucchini_lib") {
"disassembler.cc", "disassembler.cc",
"disassembler.h", "disassembler.h",
"image_utils.h", "image_utils.h",
"io_utils.cc",
"io_utils.h",
"suffix_array.h", "suffix_array.h",
"typed_value.h", "typed_value.h",
] ]
...@@ -45,6 +47,7 @@ test("zucchini_unittests") { ...@@ -45,6 +47,7 @@ test("zucchini_unittests") {
sources = [ sources = [
"buffer_view_unittest.cc", "buffer_view_unittest.cc",
"crc32_unittest.cc", "crc32_unittest.cc",
"io_utils_unittest.cc",
"suffix_array_unittest.cc", "suffix_array_unittest.cc",
"typed_value_unittest.cc", "typed_value_unittest.cc",
] ]
......
// Copyright 2017 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 "chrome/installer/zucchini/io_utils.h"
#include <iostream>
namespace zucchini {
/******** LimitedOutputStream::StreamBuf ********/
LimitedOutputStream::StreamBuf::StreamBuf(std::ostream& os, int limit)
: os_(os), limit_(limit) {}
LimitedOutputStream::StreamBuf::~StreamBuf() {
// Display warning in case we forget to flush data with std::endl.
if (!str().empty()) {
std::cerr << "Warning: LimitedOutputStream has " << str().length()
<< " bytes of unflushed output." << std::endl;
}
}
int LimitedOutputStream::StreamBuf::sync() {
if (full()) {
str("");
return 0;
}
os_ << str();
str("");
if (++counter_ >= limit_)
os_ << "(Additional output suppressed)\n";
os_.flush();
return 0;
}
/******** LimitedOutputStream ********/
LimitedOutputStream::LimitedOutputStream(std::ostream& os, int limit)
: std::ostream(&buf_), buf_(os, limit) {}
/******** PrefixSep ********/
std::ostream& operator<<(std::ostream& ostr, PrefixSep& obj) {
if (obj.first_)
obj.first_ = false;
else
ostr << obj.sep_str_;
return ostr;
}
} // namespace zucchini
// Copyright 2017 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 CHROME_INSTALLER_ZUCCHINI_IO_UTILS_H_
#define CHROME_INSTALLER_ZUCCHINI_IO_UTILS_H_
#include <cctype>
#include <istream>
#include <ostream>
#include <sstream>
#include <string>
#include "base/macros.h"
namespace zucchini {
// An std::ostream wrapper that that limits number of std::endl lines to output,
// useful for preventing excessive debug message output. Usage requires some
// work by the caller. Sample:
// static LimitedOutputStream los(std::cerr, 10);
// if (!los.full()) {
// ... // Prepare message. Block may be skipped so don't do other work!
// los << message;
// los << std::endl; // Important!
// }
class LimitedOutputStream : public std::ostream {
private:
class StreamBuf : public std::stringbuf {
public:
StreamBuf(std::ostream& os, int limit);
~StreamBuf();
int sync() override;
bool full() const { return counter_ >= limit_; }
private:
std::ostream& os_;
const int limit_;
int counter_ = 0;
};
public:
LimitedOutputStream(std::ostream& os, int limit);
bool full() const { return buf_.full(); }
private:
StreamBuf buf_;
DISALLOW_COPY_AND_ASSIGN(LimitedOutputStream);
};
// A class to render hexadecimal numbers for std::ostream with 0-padding. This
// is more concise and flexible than stateful STL manipulator alternatives; so:
// std::ios old_fmt(nullptr);
// old_fmt.copyfmt(std::cout);
// std::cout << std::uppercase << std::hex;
// std::cout << std::setfill('0') << std::setw(8) << int_data << std::endl;
// std::cout.copyfmt(old_fmt);
// can be expressed as:
// std::cout << AxHex<8>(int_data) << std::endl;
template <int N, typename T = uint32_t>
struct AsHex {
explicit AsHex(T value_in) : value(value_in) {}
T value;
};
template <int N, typename T>
std::ostream& operator<<(std::ostream& os, const AsHex<N, T>& as_hex) {
char buf[N + 1];
buf[N] = '\0';
T value = as_hex.value;
for (int i = N - 1; i >= 0; --i, value >>= 4)
buf[i] = "0123456789ABCDEF"[static_cast<int>(value & 0x0F)];
if (value)
os << "..."; // To indicate data truncation, or negative values.
os << buf;
return os;
}
// An output manipulator to simplify printing list separators. Sample usage:
// PrefixSep sep(",");
// for (int i : {3, 1, 4, 1, 5, 9})
// std::cout << sep << i;
// std::cout << std::endl; // Outputs "3,1,4,1,5,9\n".
class PrefixSep {
public:
explicit PrefixSep(const std::string& sep_str) : sep_str_(sep_str) {}
friend std::ostream& operator<<(std::ostream& ostr, PrefixSep& obj);
private:
std::string sep_str_;
bool first_ = true;
DISALLOW_COPY_AND_ASSIGN(PrefixSep);
};
// An input manipulator that dictates the expected next character in
// |std::istream|, and invalidates the stream if expectation is not met.
class EatChar {
public:
explicit EatChar(char ch) : ch_(ch) {}
friend inline std::istream& operator>>(std::istream& istr,
const EatChar& obj) {
if (!istr.fail() && istr.get() != obj.ch_)
istr.setstate(std::ios_base::failbit);
return istr;
}
private:
char ch_;
DISALLOW_COPY_AND_ASSIGN(EatChar);
};
// An input manipulator that reads an unsigned integer from |std::istream|,
// and invalidates the stream on failure. Intolerant of leading white spaces,
template <typename T>
class StrictUInt {
public:
explicit StrictUInt(T& var) : var_(var) {}
StrictUInt(const StrictUInt&) = default;
friend std::istream& operator>>(std::istream& istr, StrictUInt<T> obj) {
if (!istr.fail() && !::isdigit(istr.peek())) {
istr.setstate(std::ios_base::failbit);
return istr;
}
return istr >> obj.var_;
}
private:
T& var_;
};
// Stub out uint8_t: istream treats it as char, and value won't be read as int!
template <>
struct StrictUInt<uint8_t> {};
} // namespace zucchini
#endif // CHROME_INSTALLER_ZUCCHINI_IO_UTILS_H_
// Copyright 2017 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 "chrome/installer/zucchini/io_utils.h"
#include <sstream>
#include <string>
#include "base/logging.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace zucchini {
TEST(IOUtilsTest, LimitedOutputStream) {
std::ostringstream oss;
LimitedOutputStream los(oss, 3);
EXPECT_FALSE(los.full());
EXPECT_EQ("", oss.str());
// Line 1.
los << "a" << 1 << "b" << 2 << "c" << 3 << std::endl;
EXPECT_FALSE(los.full());
EXPECT_EQ("a1b2c3\n", oss.str());
// Line 2.
oss.str("");
los << "\r\r\n\n" << std::endl; // Manual new lines don't count.
EXPECT_FALSE(los.full());
EXPECT_EQ("\r\r\n\n\n", oss.str());
// Line 3.
oss.str("");
los << "blah" << 137;
EXPECT_FALSE(los.full());
los << std::endl;
EXPECT_TRUE(los.full());
EXPECT_EQ("blah137\n(Additional output suppressed)\n", oss.str());
// Not testing adding more lines: the behavior is undefined since we rely on
// caller suppressing output if |los.full()| is true.
}
TEST(IOUtilsTest, AsHex) {
std::ostringstream oss;
// Helper for single-line tests. Eats dummy std::ostream& from operator<<().
auto extract = [&oss](std::ostream&) -> std::string {
std::string ret = oss.str();
oss.str("");
return ret;
};
EXPECT_EQ("00000000", extract(oss << AsHex<8>(0)));
EXPECT_EQ("12345678", extract(oss << AsHex<8>(0x12345678U)));
EXPECT_EQ("9ABCDEF0", extract(oss << AsHex<8>(0x9ABCDEF0U)));
EXPECT_EQ("(00000064)", extract(oss << "(" << AsHex<8>(100) << ")"));
EXPECT_EQ("00FFFF", extract(oss << AsHex<6>(0xFFFFU)));
EXPECT_EQ("FFFF", extract(oss << AsHex<4>(0xFFFFU)));
EXPECT_EQ("...FF", extract(oss << AsHex<2>(0xFFFFU)));
EXPECT_EQ("...00", extract(oss << AsHex<2>(0x100U)));
EXPECT_EQ("FF\n", extract(oss << AsHex<2>(0xFFU) << std::endl));
EXPECT_EQ("132457689BACDEF0",
extract(oss << AsHex<16, uint64_t>(0x132457689BACDEF0LLU)));
EXPECT_EQ("000000000001", extract(oss << AsHex<12, uint8_t>(1)));
EXPECT_EQ("00000089", extract(oss << AsHex<8, int32_t>(137)));
EXPECT_EQ("...FFFFFFFF", extract(oss << AsHex<8, int32_t>(-1)));
EXPECT_EQ("7FFF", extract(oss << AsHex<4, int16_t>(0x7FFFU)));
EXPECT_EQ("...8000", extract(oss << AsHex<4, int16_t>(0x8000U)));
EXPECT_EQ("8000", extract(oss << AsHex<4, uint16_t>(0x8000U)));
}
TEST(IOUtilsTest, PrefixSep) {
std::ostringstream oss;
PrefixSep sep(",");
oss << sep << 3;
EXPECT_EQ("3", oss.str());
oss << sep << 1;
EXPECT_EQ("3,1", oss.str());
oss << sep << 4 << sep << 1 << sep << "59";
EXPECT_EQ("3,1,4,1,59", oss.str());
}
TEST(IOUtilsTest, PrefixSepAlt) {
std::ostringstream oss;
PrefixSep sep(" ");
oss << sep << 3;
EXPECT_EQ("3", oss.str());
oss << sep << 1;
EXPECT_EQ("3 1", oss.str());
oss << sep << 4 << sep << 1 << sep << "59";
EXPECT_EQ("3 1 4 1 59", oss.str());
}
TEST(IOUtilsTest, EatChar) {
std::istringstream main_iss;
// Helper for single-line tests.
auto iss = [&main_iss](const std::string s) -> std::istringstream& {
main_iss.clear();
main_iss.str(s);
return main_iss;
};
EXPECT_TRUE(iss("a,1") >> EatChar('a') >> EatChar(',') >> EatChar('1'));
EXPECT_FALSE(iss("a,a") >> EatChar('a') >> EatChar(',') >> EatChar('1'));
EXPECT_FALSE(iss("a") >> EatChar('a') >> EatChar(',') >> EatChar('1'));
EXPECT_FALSE(iss("x") >> EatChar('X'));
EXPECT_TRUE(iss("_\n") >> EatChar('_') >> EatChar('\n'));
}
TEST(IOUtilsTest, StrictUInt) {
std::istringstream main_iss;
// Helper for single-line tests.
auto iss = [&main_iss](const std::string& s) -> std::istringstream& {
main_iss.clear();
main_iss.str(s);
return main_iss;
};
uint32_t u32 = 0;
EXPECT_TRUE(iss("1234") >> StrictUInt<uint32_t>(u32));
EXPECT_EQ(uint32_t(1234), u32);
EXPECT_TRUE(iss("001234") >> StrictUInt<uint32_t>(u32));
EXPECT_EQ(uint32_t(1234), u32);
EXPECT_FALSE(iss("blahblah") >> StrictUInt<uint32_t>(u32));
EXPECT_EQ(uint32_t(1234), u32); // No overwrite on failure.
EXPECT_TRUE(iss("137suffix") >> StrictUInt<uint32_t>(u32));
EXPECT_EQ(uint32_t(137), u32);
EXPECT_FALSE(iss(" 1234") >> StrictUInt<uint32_t>(u32));
EXPECT_FALSE(iss("-1234") >> StrictUInt<uint32_t>(u32));
uint16_t u16 = 0;
EXPECT_TRUE(iss("65535") >> StrictUInt<uint16_t>(u16));
EXPECT_EQ(uint16_t(65535), u16);
EXPECT_FALSE(iss("65536") >> StrictUInt<uint16_t>(u16)); // Overflow.
uint64_t u64 = 0;
EXPECT_TRUE(iss("1000000000001") >> StrictUInt<uint64_t>(u64));
EXPECT_EQ(uint64_t(1000000000001LL), u64);
// uint8_t is stubbed out, so no tests for it.
}
TEST(IOUtilsTest, ParseSimpleEquations) {
std::istringstream iss("123+456=579,4-3=1");
uint32_t a = 0;
uint32_t b = 0;
uint32_t c = 0;
EXPECT_TRUE(iss >> StrictUInt<uint32_t>(a) >> EatChar('+') >>
StrictUInt<uint32_t>(b) >> EatChar('=') >>
StrictUInt<uint32_t>(c));
EXPECT_EQ(uint32_t(123), a);
EXPECT_EQ(uint32_t(456), b);
EXPECT_EQ(uint32_t(579), c);
EXPECT_TRUE(iss >> EatChar(','));
EXPECT_TRUE(iss >> StrictUInt<uint32_t>(a) >> EatChar('-') >>
StrictUInt<uint32_t>(b) >> EatChar('=') >>
StrictUInt<uint32_t>(c));
EXPECT_EQ(uint32_t(4), a);
EXPECT_EQ(uint32_t(3), b);
EXPECT_EQ(uint32_t(1), c);
}
} // namespace zucchini
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