Merge branch 'master' into cmake

This commit is contained in:
Sam Edwards 2019-10-15 23:43:25 -06:00
commit 12c16eb9ff
31 changed files with 4617 additions and 4343 deletions

View File

@ -247,13 +247,13 @@ class StreamIOWrapper(io.IOBase):
self.__stream.clear() # clear eof flag
self.__lastWrite = False
if size is not None and size >= 0:
result = self.__reader.extractBytes(size)
return self.__reader.extractBytes(size)
else:
# Read to end-of-file.
result = b''
result = bytearray()
while not self.__stream.eof():
result += self.__reader.extractBytes(512)
return result
result += self.__reader.extractBytes(4096)
return bytes(result)
read1 = read

File diff suppressed because it is too large Load Diff

View File

@ -1218,6 +1218,35 @@ constructor_prototype:
CPPInstanceIdentifier *ii = new CPPInstanceIdentifier($1);
ii->add_func_modifier($4, $6);
$$ = new CPPInstance(type, ii, 0, @1.file);
}
/* This is a hack to support functions with the identifier enveloped by a
pair of parentheses. */
| TYPENAME_IDENTIFIER '(' IDENTIFIER ')' '('
{
// Create a scope for this function.
CPPScope *scope = new CPPScope($3->get_scope(current_scope, global_scope),
$3->_names.back(), V_private);
// It still needs to be able to pick up any template arguments, if this is
// a definition for a method template. Add a fake "using" declaration to
// accomplish this.
scope->_using.insert(current_scope);
push_scope(scope);
}
function_parameter_list ')' function_post
{
pop_scope();
CPPType *type = $1->find_type(current_scope, global_scope, false, current_lexer);
if (type == nullptr) {
yyerror(string("internal error resolving type ") + $1->get_fully_scoped_name(), @1);
}
assert(type != nullptr);
CPPInstanceIdentifier *ii = new CPPInstanceIdentifier($3);
ii->add_func_modifier($7, $9);
$$ = new CPPInstance(type, ii, 0, @1.file);
}
| TYPENAME_IDENTIFIER '('

View File

@ -1942,7 +1942,22 @@ get_identifier(int c) {
// Is it a manifest?
Manifests::const_iterator mi = _manifests.find(name);
if (mi != _manifests.end() && !should_ignore_manifest((*mi).second)) {
return expand_manifest((*mi).second);
// If the manifest is expecting arguments, we don't expand it unless the
// the next token is an open-parenthesis.
CPPManifest *manifest = (*mi).second;
if (manifest->_has_parameters) {
while (c != EOF && isspace(c)) {
get();
c = peek();
}
if (c == '(') {
// It is followed by a parenthesis, so we can expand this.
return expand_manifest(manifest);
}
} else {
// Non-function-like macros are always expanded.
return expand_manifest(manifest);
}
}
if (name == "__FILE__") {
return get_literal(SIMPLE_STRING, loc, loc.file._filename_as_referenced);

View File

@ -445,15 +445,16 @@ get_call_str(const string &container, const vector_string &pexprs) const {
} else if (_has_this && !container.empty()) {
// If we have a "this" parameter, the calling convention is also a bit
// different.
call << "(";
call << "((";
_parameters[0]._remap->pass_parameter(call, container);
call << ")." << _cppfunc->get_local_name();
call << ")." << _cppfunc->get_local_name() << ")";
} else {
call << "(";
if (_cpptype != nullptr) {
call << _cpptype->get_local_name(&parser);
}
call << "::" << _cppfunc->get_local_name();
call << "::" << _cppfunc->get_local_name() << ")";
}
}
call << "(";

View File

@ -26,14 +26,20 @@ get_string() {
// First, get the length of the string
size_t size = get_uint16();
std::string result(size, 0);
if (size == 0) {
return string();
return result;
}
char *buffer = (char *)alloca(size);
_in->read(buffer, size);
_in->read(&result[0], size);
size_t read_bytes = _in->gcount();
return string(buffer, read_bytes);
if (read_bytes == size) {
return result;
} else {
return result.substr(0, read_bytes);
}
}
/**
@ -82,17 +88,17 @@ string StreamReader::
get_fixed_string(size_t size) {
nassertr(!_in->eof() && !_in->fail(), string());
std::string result(size, 0);
if (size == 0) {
return string();
return result;
}
char *buffer = (char *)alloca(size);
_in->read(buffer, size);
_in->read(&result[0], size);
size_t read_bytes = _in->gcount();
string result(buffer, read_bytes);
result.resize(read_bytes);
size_t zero_byte = result.find('\0');
return result.substr(0, zero_byte);
return result.substr(0, std::min(zero_byte, read_bytes));
}
/**

View File

@ -21,14 +21,20 @@
*/
PyObject *Extension<StreamReader>::
extract_bytes(size_t size) {
unsigned char *buffer = (unsigned char *)alloca(size);
size_t read_bytes = _this->extract_bytes(buffer, size);
std::istream *in = _this->get_istream();
if (in->eof() || in->fail() || size == 0) {
return PyBytes_FromStringAndSize(nullptr, 0);
}
#if PY_MAJOR_VERSION >= 3
return PyBytes_FromStringAndSize((char *)buffer, read_bytes);
#else
return PyString_FromStringAndSize((char *)buffer, read_bytes);
#endif
PyObject *bytes = PyBytes_FromStringAndSize(nullptr, size);
in->read(PyBytes_AS_STRING(bytes), size);
size_t read_bytes = in->gcount();
if (read_bytes == size || _PyBytes_Resize(&bytes, read_bytes) == 0) {
return bytes;
} else {
return nullptr;
}
}
/**

View File

@ -351,7 +351,7 @@ traverse(const NodePath &root) {
CollisionBox::flush_level();
}
#ifdef DO_COLLISION_RECORDING
#if defined(DO_COLLISION_RECORDING) || !defined(CPPPARSER)
/**
* Uses the indicated CollisionRecorder object to start recording the
* intersection tests made by each subsequent call to traverse() on this
@ -370,6 +370,7 @@ traverse(const NodePath &root) {
*/
void CollisionTraverser::
set_recorder(CollisionRecorder *recorder) {
#ifdef DO_COLLISION_RECORDING
if (recorder != _recorder) {
// Remove the old recorder, if any.
if (_recorder != nullptr) {
@ -389,6 +390,7 @@ set_recorder(CollisionRecorder *recorder) {
_recorder->_trav = this;
}
}
#endif
}
/**
@ -399,11 +401,15 @@ set_recorder(CollisionRecorder *recorder) {
*/
CollisionVisualizer *CollisionTraverser::
show_collisions(const NodePath &root) {
#ifdef DO_COLLISION_RECORDING
hide_collisions();
CollisionVisualizer *viz = new CollisionVisualizer("show_collisions");
_collision_visualizer_np = root.attach_new_node(viz);
set_recorder(viz);
return viz;
#else
return nullptr;
#endif
}
/**
@ -411,10 +417,12 @@ show_collisions(const NodePath &root) {
*/
void CollisionTraverser::
hide_collisions() {
#ifdef DO_COLLISION_RECORDING
if (!_collision_visualizer_np.is_empty()) {
_collision_visualizer_np.remove_node();
}
clear_recorder();
#endif
}
#endif // DO_COLLISION_RECORDING

View File

@ -64,7 +64,7 @@ PUBLISHED:
void traverse(const NodePath &root);
#ifdef DO_COLLISION_RECORDING
#if defined(DO_COLLISION_RECORDING) || !defined(CPPPARSER)
void set_recorder(CollisionRecorder *recorder);
INLINE bool has_recorder() const;
INLINE CollisionRecorder *get_recorder() const;
@ -132,6 +132,9 @@ private:
#ifdef DO_COLLISION_RECORDING
CollisionRecorder *_recorder;
NodePath _collision_visualizer_np;
#else
CollisionRecorder *_recorder_disabled = nullptr;
NodePath _collision_visualizer_np_disabled;
#endif // DO_COLLISION_RECORDING
// Statistics

View File

@ -33,7 +33,7 @@
* achieve this.
*/
class EXPCL_PANDA_COLLIDE CollisionVisualizer : public PandaNode, public CollisionRecorder {
PUBLISHED:
public:
explicit CollisionVisualizer(const std::string &name);
CollisionVisualizer(const CollisionVisualizer &copy);
virtual ~CollisionVisualizer();
@ -46,10 +46,6 @@ PUBLISHED:
void clear();
PUBLISHED:
MAKE_PROPERTY(point_scale, get_point_scale, set_point_scale);
MAKE_PROPERTY(normal_scale, get_normal_scale, set_normal_scale);
public:
// from parent class PandaNode.
virtual PandaNode *make_copy() const;

View File

@ -461,7 +461,6 @@ traverse_prepared_textures(GraphicsStateGuardian::TextureCallback *func,
}
}
#ifndef NDEBUG
/**
* Sets the "flash texture". This is a debug feature; when enabled, the
* specified texture will begin flashing in the scene, helping you to find it
@ -481,31 +480,34 @@ traverse_prepared_textures(GraphicsStateGuardian::TextureCallback *func,
*/
void GraphicsStateGuardian::
set_flash_texture(Texture *tex) {
_flash_texture = tex;
}
#endif // NDEBUG
#ifndef NDEBUG
_flash_texture = tex;
#endif
}
/**
* Resets the "flash texture", so that no textures will flash. See
* set_flash_texture().
*/
void GraphicsStateGuardian::
clear_flash_texture() {
_flash_texture = nullptr;
}
#endif // NDEBUG
#ifndef NDEBUG
_flash_texture = nullptr;
#endif
}
/**
* Returns the current "flash texture", if any, or NULL if none. See
* set_flash_texture().
*/
Texture *GraphicsStateGuardian::
get_flash_texture() const {
#ifndef NDEBUG
return _flash_texture;
#else
return nullptr;
#endif
}
#endif // NDEBUG
/**
* Sets the SceneSetup object that indicates the initial camera position, etc.

View File

@ -259,7 +259,7 @@ PUBLISHED:
typedef bool TextureCallback(TextureContext *tc, void *callback_arg);
void traverse_prepared_textures(TextureCallback *func, void *callback_arg);
#ifndef NDEBUG
#if !defined(NDEBUG) || !defined(CPPPARSER)
void set_flash_texture(Texture *tex);
void clear_flash_texture();
Texture *get_flash_texture() const;
@ -656,6 +656,8 @@ protected:
#ifndef NDEBUG
PT(Texture) _flash_texture;
#else
PT(Texture) _flash_texture_unused;
#endif
public:

View File

@ -13,11 +13,9 @@
#include "pStatCollectorForwardBase.h"
#ifdef DO_PSTATS
/**
*
*/
PStatCollectorForwardBase::
~PStatCollectorForwardBase() {
}
#endif // DO_PSTATS

View File

@ -27,10 +27,13 @@
*/
class EXPCL_PANDA_EXPRESS PStatCollectorForwardBase : public ReferenceCount {
PUBLISHED:
#ifdef DO_PSTATS
virtual ~PStatCollectorForwardBase();
#ifdef DO_PSTATS
virtual void add_level(double level)=0;
#else
// We still need to declare a virtual destructor for ABI compatibility, so
// that a vtable is created.
INLINE void add_level(double level) { }
#endif
};

View File

@ -13,9 +13,9 @@
#include "geomVertexReader.h"
#ifndef NDEBUG
// This is defined just for the benefit of having something non-NULL to
// return from a nassertr() call.
#ifdef _DEBUG
// This is defined just for the benefit of having something non-NULL to
// return from a nassertr() call.
const unsigned char GeomVertexReader::empty_buffer[100] = { 0 };
#endif

View File

@ -155,7 +155,7 @@ private:
int _start_row;
bool _force;
#ifndef NDEBUG
#ifdef _DEBUG
// This is defined just for the benefit of having something non-NULL to
// return from a nassertr() call.
static const unsigned char empty_buffer[100];

View File

@ -23,12 +23,4 @@ NotifyCategoryDecl(particlesystem, EXPCL_PANDA_PARTICLESYSTEM, EXPTP_PANDA_PARTI
extern EXPCL_PANDA_PARTICLESYSTEM void init_libparticlesystem();
#ifndef NDEBUG //[
// Non-release build:
#define PARTICLE_SYSTEM_DEBUG
#else //][
// Release build:
#undef PARTICLE_SYSTEM_DEBUG
#endif //]
#endif // CONFIG_PARTICLESYSTEM_H

View File

@ -11,12 +11,6 @@
* @date 2000-06-14
*/
#ifndef NDEBUG
// #define PSDEBUG
#endif
// #define PSSANITYCHECK
#ifndef PARTICLESYSTEM_H
#define PARTICLESYSTEM_H

View File

@ -85,9 +85,7 @@ private:
typedef pvector< PT(CullBin) > Bins;
Bins _bins;
#ifndef NDEBUG
bool _show_transparency;
#endif
bool _show_transparency = false;
public:
static TypeHandle get_class_type() {

View File

@ -82,9 +82,6 @@ PandaNode(const string &name) :
pgraph_cat.debug()
<< "Constructing " << (void *)this << ", " << get_name() << "\n";
}
#ifndef NDEBUG
_unexpected_change_flags = 0;
#endif // !NDEBUG
#ifdef DO_MEMORY_USAGE
MemoryUsage::update_type(this, this);
@ -135,7 +132,8 @@ PandaNode(const PandaNode &copy) :
Namable(copy),
_paths_lock("PandaNode::_paths_lock"),
_dirty_prev_transform(false),
_python_tag_data(copy._python_tag_data)
_python_tag_data(copy._python_tag_data),
_unexpected_change_flags(0)
{
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
@ -144,10 +142,6 @@ PandaNode(const PandaNode &copy) :
#ifdef DO_MEMORY_USAGE
MemoryUsage::update_type(this, this);
#endif
// Copying a node does not copy its children.
#ifndef NDEBUG
_unexpected_change_flags = 0;
#endif // !NDEBUG
// Need to have this held before we grab any other locks.
LightMutexHolder holder(_dirty_prev_transforms._lock);

View File

@ -532,9 +532,7 @@ private:
};
PT(PythonTagData) _python_tag_data;
#ifndef NDEBUG
unsigned int _unexpected_change_flags;
#endif // !NDEBUG
unsigned int _unexpected_change_flags = 0;
// This is the data that must be cycled between pipeline stages.

View File

@ -28,8 +28,6 @@ extern EXPCL_PANDA_PHYSICS void init_libphysics();
// value of bar is " << bar);
#ifndef NDEBUG //[
// Non-release build:
#define PHYSICS_DEBUG
#define physics_spam(msg) \
if (physics_cat.is_spam()) { \
physics_cat->spam() << msg << std::endl; \
@ -50,8 +48,6 @@ extern EXPCL_PANDA_PHYSICS void init_libphysics();
physics_cat->error() << msg << std::endl
#else //][
// Release build:
#undef PHYSICS_DEBUG
#define physics_spam(msg) ((void)0)
#define physics_debug(msg) ((void)0)
#define physics_info(msg) ((void)0)
@ -59,7 +55,4 @@ extern EXPCL_PANDA_PHYSICS void init_libphysics();
#define physics_error(msg) ((void)0)
#endif //]
#define audio_error(msg) \
audio_cat->error() << msg << std::endl
#endif // CONFIG_PHYSICS_H

View File

@ -87,14 +87,14 @@ PUBLISHED:
virtual LMatrix4 get_lcs() const;
virtual PhysicsObject *make_copy() const;
#ifndef NDEBUG
void set_name(const std::string &name) {
_name = name;
}
const std::string& get_name() {
return _name;
}
#endif
#if !defined(NDEBUG) || !defined(CPPPARSER)
void set_name(const std::string &name) {
_name = name;
}
const std::string &get_name() {
return _name;
}
#endif
virtual void output(std::ostream &out) const;
virtual void write(std::ostream &out, int indent=0) const;
@ -115,9 +115,7 @@ private:
bool _process_me;
bool _oriented;
#ifndef NDEBUG
std::string _name;
#endif
std::string _name;
public:
static TypeHandle get_class_type() {

View File

@ -1224,6 +1224,48 @@ InternalThread(const string &name, const string &sync_name) :
#else // DO_PSTATS
void PStatClient::
set_client_name(const std::string &name) {
}
std::string PStatClient::
get_client_name() const {
return std::string();
}
void PStatClient::
set_max_rate(double rate) {
}
double PStatClient::
get_max_rate() const {
return 0.0;
}
PStatCollector PStatClient::
get_collector(int index) const {
return PStatCollector();
}
std::string PStatClient::
get_collector_name(int index) const {
return std::string();
}
std::string PStatClient::
get_collector_fullname(int index) const {
return std::string();
}
PStatThread PStatClient::
get_thread(int index) const {
return PStatThread((PStatClient *)this, 0);
}
double PStatClient::
get_real_time() const {
}
PStatThread PStatClient::
get_main_thread() const {
return PStatThread((PStatClient *)this, 0);
@ -1239,12 +1281,63 @@ make_collector_with_relname(int parent_index, std::string relname) {
return PStatCollector();
}
PStatThread PStatClient::
make_thread(Thread *thread) {
return PStatThread((PStatClient *)this, 0);
}
void PStatClient::
main_tick() {
}
void PStatClient::
thread_tick(const std::string &) {
}
void PStatClient::
client_main_tick() {
}
void PStatClient::
client_thread_tick(const std::string &sync_name) {
}
bool PStatClient::
client_connect(std::string hostname, int port) {
return false;
}
void PStatClient::
client_disconnect() {
return;
}
bool PStatClient::
client_is_connected() const {
return false;
}
void PStatClient::
client_resume_after_pause() {
return;
}
PStatClient *PStatClient::
get_global_pstats() {
static PStatClient global_pstats;
return &global_pstats;
}
bool PStatClient::
is_active(int collector_index, int thread_index) const {
return false;
}
bool PStatClient::
is_started(int collector_index, int thread_index) const {
return false;
}
void PStatClient::
start(int collector_index, int thread_index) {
}
@ -1261,5 +1354,21 @@ void PStatClient::
stop(int collector_index, int thread_index, double as_of) {
}
void PStatClient::
clear_level(int collector_index, int thread_index) {
}
void PStatClient::
set_level(int collector_index, int thread_index, double level) {
}
void PStatClient::
add_level(int collector_index, int thread_index, double increment) {
}
double PStatClient::
get_level(int collector_index, int thread_index) const {
return 0.0;
}
#endif // DO_PSTATS

View File

@ -265,20 +265,43 @@ public:
PStatClient() { }
~PStatClient() { }
PUBLISHED:
std::string get_collector_name(int index) const { return std::string(); }
std::string get_collector_fullname(int index) const { return std::string(); }
void set_client_name(const std::string &name);
std::string get_client_name() const;
void set_max_rate(double rate);
double get_max_rate() const;
PStatCollector get_collector(int index) const;
std::string get_collector_name(int index) const;
std::string get_collector_fullname(int index) const;
INLINE int get_num_threads() const { return 0; }
PStatThread get_thread(int index) const;
INLINE std::string get_thread_name(int index) const { return ""; }
INLINE std::string get_thread_sync_name(int index) const { return ""; }
INLINE PT(Thread) get_thread_object(int index) const { return nullptr; }
PStatThread get_main_thread() const;
PStatThread get_current_thread() const;
double get_real_time() const;
PUBLISHED:
INLINE static bool connect(const std::string & = std::string(), int = -1) { return false; }
INLINE static void disconnect() { }
INLINE static bool is_connected() { return false; }
INLINE static void resume_after_pause() { }
INLINE static void main_tick() { }
INLINE static void thread_tick(const std::string &) { }
static void main_tick();
static void thread_tick(const std::string &);
public:
void client_main_tick();
void client_thread_tick(const std::string &sync_name);
bool client_connect(std::string hostname, int port);
void client_disconnect();
bool client_is_connected() const;
void client_resume_after_pause();
static PStatClient *get_global_pstats();
@ -286,19 +309,20 @@ private:
// These are used by inline PStatCollector methods, so they need to be
// stubbed out for ABI compatibility.
PStatCollector make_collector_with_relname(int parent_index, std::string relname);
PStatThread make_thread(Thread *thread);
bool is_active(int collector_index, int thread_index) const { return false; }
bool is_started(int collector_index, int thread_index) const { return false; }
bool is_active(int collector_index, int thread_index) const;
bool is_started(int collector_index, int thread_index) const;
void start(int collector_index, int thread_index);
void start(int collector_index, int thread_index, double as_of);
void stop(int collector_index, int thread_index);
void stop(int collector_index, int thread_index, double as_of);
void clear_level(int collector_index, int thread_index) { }
void set_level(int collector_index, int thread_index, double level) { }
void add_level(int collector_index, int thread_index, double increment) { }
double get_level(int collector_index, int thread_index) const { return 0.0; }
void clear_level(int collector_index, int thread_index);
void set_level(int collector_index, int thread_index, double level);
void add_level(int collector_index, int thread_index, double increment);
double get_level(int collector_index, int thread_index) const;
};
#endif // DO_PSTATS

View File

@ -13,12 +13,12 @@
#include "pStatCollectorForward.h"
#ifdef DO_PSTATS
/**
*
*/
void PStatCollectorForward::
add_level(double increment) {
#ifdef DO_PSTATS
_col.add_level_now(increment);
}
#endif // DO_PSTATS
}

View File

@ -27,9 +27,9 @@ class EXPCL_PANDA_PSTATCLIENT PStatCollectorForward : public PStatCollectorForwa
PUBLISHED:
INLINE PStatCollectorForward(const PStatCollector &col);
#ifdef DO_PSTATS
virtual void add_level(double level);
#ifdef DO_PSTATS
private:
PStatCollector _col;
#endif

View File

@ -44,10 +44,6 @@ const uint32_t UniqueIdAllocator::IndexAllocated = (uint32_t)-2;
#define uniqueIdAllocator_warning(msg) ((void)0)
#endif //]
#define audio_error(msg) \
audio_cat->error() << msg << endl
/**
* Create a free id pool in the range [min:max].
*/

View File

@ -185,7 +185,7 @@ write(std::ostream &out, int indent_level) const {
}
}
#ifndef NDEBUG
#if !defined(NDEBUG) || !defined(CPPPARSER)
/**
* Enables the visualization of all of the regions handled by this
* MouseWatcherBase. The supplied NodePath should be the root of the 2-d
@ -193,44 +193,52 @@ write(std::ostream &out, int indent_level) const {
*/
void MouseWatcherBase::
show_regions(const NodePath &render2d, const std::string &bin_name, int draw_order) {
#ifndef NDEBUG
LightMutexHolder holder(_lock);
do_show_regions(render2d, bin_name, draw_order);
#endif
}
#endif // NDEBUG
#ifndef NDEBUG
#if !defined(NDEBUG) || !defined(CPPPARSER)
/**
* Specifies the color used to draw the region rectangles for the regions
* visualized by show_regions().
*/
void MouseWatcherBase::
set_color(const LColor &color) {
#ifndef NDEBUG
LightMutexHolder holder(_lock);
_color = color;
do_update_regions();
#endif
}
#endif // NDEBUG
#ifndef NDEBUG
#if !defined(NDEBUG) || !defined(CPPPARSER)
/**
* Stops the visualization created by a previous call to show_regions().
*/
void MouseWatcherBase::
hide_regions() {
#ifndef NDEBUG
LightMutexHolder holder(_lock);
do_hide_regions();
#endif
}
#endif // NDEBUG
#ifndef NDEBUG
#if !defined(NDEBUG) || !defined(CPPPARSER)
/**
* Refreshes the visualization created by show_regions().
*/
void MouseWatcherBase::
update_regions() {
#ifndef NDEBUG
LightMutexHolder holder(_lock);
do_update_regions();
#endif
}
#endif // NDEBUG

View File

@ -53,7 +53,7 @@ PUBLISHED:
void output(std::ostream &out) const;
void write(std::ostream &out, int indent_level = 0) const;
#ifndef NDEBUG
#if !defined(NDEBUG) || !defined(CPPPARSER)
void show_regions(const NodePath &render2d,
const std::string &bin_name, int draw_order);
void set_color(const LColor &color);
@ -84,15 +84,20 @@ protected:
LightMutex _lock;
private:
typedef pvector< PT(PandaNode) > Vizzes;
#ifndef NDEBUG
PandaNode *make_viz_region(MouseWatcherRegion *region);
typedef pvector< PT(PandaNode) > Vizzes;
Vizzes _vizzes;
bool _show_regions;
NodePath _show_regions_root;
LColor _color;
#else
Vizzes _vizzes_disabled;
bool _show_regions_disabled = false;
NodePath _show_regions_root_disabled;
LColor _color_disabled;
#endif // NDEBUG
public:

View File

@ -155,3 +155,38 @@ def test_streamreader_readline():
stream = StringStream(b'\x00\x00')
reader = StreamReader(stream, False)
assert reader.readline() == b'\x00\x00'
def test_streamreader_extract_bytes():
# Empty bytes
stream = StringStream(b'')
reader = StreamReader(stream, False)
assert reader.extract_bytes(10) == b''
# Small bytes object, small reads
stream = StringStream(b'abcd')
reader = StreamReader(stream, False)
assert reader.extract_bytes(2) == b'ab'
assert reader.extract_bytes(2) == b'cd'
assert reader.extract_bytes(2) == b''
# Embedded null bytes
stream = StringStream(b'a\x00b\x00c')
reader = StreamReader(stream, False)
assert reader.extract_bytes(5) == b'a\x00b\x00c'
# Not enough data in stream to fill buffer
stream = StringStream(b'abcdefghijklmnop')
reader = StreamReader(stream, False)
assert reader.extract_bytes(10) == b'abcdefghij'
assert reader.extract_bytes(10) == b'klmnop'
assert reader.extract_bytes(10) == b''
# Read of 0 bytes
stream = StringStream(b'abcd')
reader = StreamReader(stream, False)
assert reader.extract_bytes(0) == b''
assert reader.extract_bytes(0) == b''
# Very large read (8 MiB buffer allocation)
assert reader.extract_bytes(8 * 1024 * 1024) == b'abcd'