diff --git a/.augment-guidelines b/.augment-guidelines index ed7c6602d..ffe99d33c 100644 --- a/.augment-guidelines +++ b/.augment-guidelines @@ -1,278 +1,48 @@ -# Augment Guidelines for SRS Repository +# SRS Repository Guidelines project: name: "SRS (Simple Realtime Server)" - description: "A C++ streaming media server supporting RTMP, WebRTC, WHIP, WHEP, SRT, HLS, and HTTP-FLV" - type: "media-server" + type: "C++ streaming media server (RTMP/WebRTC/WHIP/WHEP/SRT/HLS/HTTP-FLV)" architecture: - overview: | - Core C++ streaming server with protocol implementations and media processing capabilities. - Uses State Threads (ST) for high-performance coroutine-based networking. + overview: "C++ streaming server using State Threads (ST) for coroutine-based networking" + threading: "Single-threaded coroutine architecture - no multi-threading, context switches on async I/O" + key_classes: "SrsServer (main), SrsLiveSource (source management)" - threading_model: | - Single-threaded, coroutine/goroutine-based application architecture: - - No traditional multi-threading issues (no thread switching, async race conditions) - - Uses coroutines/goroutines that cooperatively yield control - - Context switching occurs during async I/O operations - - Different context switch problems compared to multi-threaded applications: - * Coroutine state must be preserved across yields by async I/O operations - * Shared state can be modified between context switches - * Timing-dependent bugs related to when coroutines yield + webrtc: "SFU (Selective Forwarding Unit) - media relay without transcoding. Supports WHIP/WHEP. NO external TURN needed (SRS provides ICE/relay)" - key_components: - - name: "SrsServer" - description: "Main server class of live stream for RTMP/HTTP-FLV/HLS/DASH and HTTP-API" - - name: "SrsLiveSource" - description: "Central management of live stream sources for RTMP/HTTP-FLV/HLS/DASH" - - webrtc_architecture: - sfu_model: | - SRS is a WebRTC SFU (Selective Forwarding Unit) server, not a TURN server. - Key characteristics: - - Acts as a media relay/forwarding server between WebRTC clients - - Handles media routing and distribution without media processing/transcoding - - Supports WHIP (WebRTC-HTTP Ingestion Protocol) for publishing - - Supports WHEP (WebRTC-HTTP Egress Protocol) for playing/subscribing - - Does NOT require external TURN servers for most use cases - - Clients connect directly to SRS for media exchange - - no_turn_needed: | - SRS does NOT need a separate TURN server because: - - SRS itself acts as the media relay point for WebRTC sessions - - Clients establish WebRTC connections directly with SRS - - SRS handles the media forwarding between publishers and subscribers - - The SFU architecture eliminates the need for peer-to-peer TURN relay - - SRS provides the necessary ICE candidates and media routing functionality - - deployment_notes: - - "SRS can be deployed as a standalone WebRTC SFU without additional TURN infrastructure" - - "For NAT traversal, SRS provides its own ICE candidate generation" - - "External TURN servers are only needed in very specific network scenarios, not for normal SFU operation" - - "WHIP/WHEP protocols handle WebRTC signaling through HTTP, simplifying client integration" - - origin_cluster_architecture: - evolution: | - SRS Origin Cluster architecture has evolved significantly between major versions: - - SRS 6.0: Uses MESH mode origin cluster architecture (Deprecated) - - SRS 7.0: Introduces new Proxy mode architecture for improved scalability and performance (Recommended) - - srs_6_mesh_architecture: - description: "Legacy MESH mode origin cluster - deprecated in favor of SRS 7.0 Proxy mode" - characteristics: - - "Traditional origin cluster approach used in SRS 6.0" - - "Direct C++ implementation within SRS core" - - "Only supports RTMP protocol" - - "Limited scalability compared to new Proxy mode" - status: "Legacy - not recommended for new deployments" - - srs_7_proxy_architecture: - description: "New SRS Proxy mode - recommended architecture for origin clustering in SRS 7.0+" - implementation: "Go-based proxy service that forwards all protocols to specified SRS Origin server" - repository: - - Go: "https://github.com/ossrs/proxy-go" - characteristics: - - "Implemented in Go for better performance and maintainability" - - "Protocol-agnostic proxy - supports all SRS protocols (RTMP, WebRTC, WHIP, WHEP, SRT, HLS, HTTP-FLV, etc.)" - - "Forwards traffic to designated SRS Origin server" - - "Improved scalability and resource management" - - "Cleaner separation of concerns between proxy and origin functionality" - benefits: - - "Better horizontal scaling capabilities" - - "Simplified deployment and configuration" - - "Enhanced performance through Go's networking capabilities" - - "Protocol transparency - no protocol-specific handling needed in proxy layer" - - migration_guidance: - recommendation: "Use SRS 7.0 Proxy mode for all new origin cluster deployments" - legacy_support: "SRS 6.0 MESH mode architecture is maintained for backward compatibility but not recommended" - future_direction: "All future development and optimization will focus on SRS 7.0 Proxy architecture" - - deployment_patterns: - proxy_mode: - - "Deploy SRS Proxy (Go) instances as edge servers" - - "Configure proxy to forward all traffic to SRS Origin server" - - "Scale horizontally by adding more proxy instances" - - "Origin server handles actual media processing and storage" - origin_server: - - "Single or clustered SRS Origin servers handle media processing" - - "Receives all traffic forwarded from SRS Proxy instances" - - "Maintains media state and performs actual streaming operations" + origin_cluster: + v6_mesh: "DEPRECATED - C++ MESH mode, RTMP only" + v7_proxy: "RECOMMENDED - Go-based proxy (github.com/ossrs/proxy-go), all protocols, better scaling" codebase_structure: - key_directories: - - path: "trunk/src/" - description: "Main source code directory" - - path: "trunk/src/main/" - description: "Entry points including srs_main_server.cpp" - - path: "trunk/src/core/" - description: "Core platform abstractions and common definitions" - - path: "trunk/src/kernel/" - description: "Low-level codec implementations (AAC, H.264, FLV, MP4, RTC, etc.)" - - path: "trunk/src/protocol/" - description: "Protocol implementations (RTMP, HTTP, RTC, SRT, etc.)" - - path: "trunk/src/app/" - description: "High-level application logic and server components" - - key_files: - - path: "trunk/src/main/srs_main_server.cpp" - description: "Main entry point and server initialization" - - path: "trunk/src/app/srs_app_server.hpp" - description: "Core server class definition" - - path: "trunk/src/core/srs_core.hpp" - description: "Core definitions and project metadata" - - path: "trunk/src/core/srs_core_autofree.hpp" - description: "Smart pointer definitions for memory management" + dirs: "trunk/src/{main,core,kernel,protocol,app}" + key_files: "srs_main_server.cpp, srs_app_server.hpp, srs_core.hpp, srs_core_autofree.hpp" code_patterns: dependency_inversion: - principle: "MANDATORY - Classes should depend on interfaces, not concrete classes" - description: | - Follow the Dependency Inversion Principle (DIP) - high-level modules should not depend on low-level modules. - Both should depend on abstractions (interfaces). This improves testability, maintainability, and flexibility. - - In SRS, interfaces start with 'ISrs' prefix (e.g., ISrsBufferCache, ISrsMessageQueue), while concrete classes - start with 'Srs' prefix (e.g., SrsBufferCache, SrsMessageQueue). - - usage: | - WRONG: Depending on concrete class - class SrsBufferCache { - private: - SrsMessageQueue *queue_; // Direct dependency on concrete class - }; - - CORRECT: Depending on interface - class SrsBufferCache : public ISrsBufferCache { - private: - ISrsMessageQueue *queue_; // Dependency on interface - }; - + principle: "MANDATORY - Depend on interfaces (ISrs*), not concrete classes (Srs*)" rules: - - "Interfaces start with 'ISrs' prefix (e.g., ISrsMessageQueue, ISrsBufferCache, ISrsCoroutineHandler)" - - "Concrete classes start with 'Srs' prefix (e.g., SrsMessageQueue, SrsBufferCache, SrsServer)" - - "Class member variables should use interface types (ISrs*) instead of concrete types (Srs*)" - - "Classes should implement interfaces when they provide functionality that may need to be mocked or substituted" - - "Use concrete types only for instantiation, not for member variable declarations" - - benefits: - - "Enables dependency injection for unit testing" - - "Allows easy mocking of dependencies in tests" - - "Reduces coupling between components" - - "Makes code more flexible and maintainable" - - "Facilitates future refactoring and extensions" - - rationale: "Interface-based design is fundamental to testable, maintainable code. It allows components to be tested in isolation and makes the codebase more flexible for future changes." + - "Member variables use ISrs* types (e.g., ISrsMessageQueue *queue_)" + - "Concrete types only for instantiation" + - "Enables mocking for unit tests" avoid_global_variables: - principle: "MANDATORY - Never directly use global variables, convert to member fields for mockability" - description: | - Direct use of global variables makes code untestable because globals cannot be mocked. - Global variables in SRS start with _srs prefix (e.g., _srs_config, _srs_sources, _srs_stat, _srs_hooks, _srs_context). - Always store global references as member fields in the constructor so they can be replaced with mocks in tests. - - usage: | - WRONG: Direct use of global variable - srs_error_t SrsBufferCache::start() { - fast_cache_ = _srs_config->get_vhost_http_remux_fast_cache(req_->vhost_); - } - - CORRECT: Store global as member field for mockability - SrsBufferCache::SrsBufferCache(ISrsRequest *r) { - config_ = _srs_config; // Store global reference as member field - } - SrsBufferCache::~SrsBufferCache() { - config_ = NULL; // Set to NULL, do NOT free (it's a global reference) - } - srs_error_t SrsBufferCache::start() { - fast_cache_ = config_->get_vhost_http_remux_fast_cache(req_->vhost_); - } - - rules: - - "Global variables in SRS start with _srs prefix: _srs_config, _srs_sources, _srs_stat, _srs_hooks, _srs_context, etc." - - "Never access global variables directly in methods" - - "Always store global references as member fields in constructor" - - "Use interface types for member fields (e.g., ISrsAppConfig* config_)" - - "In destructor, set global reference fields to NULL but do NOT free them" - - "This allows tests to inject mock objects by setting the member field" - - common_global_variables: - core_globals: - - "_srs_log - Global logging interface (ISrsLog*)" - - "_srs_context - Thread context for coroutine management (ISrsContext*)" - - "_srs_config - Global configuration object (SrsConfig*)" - - "_srs_kernel_factory - Kernel object factory (ISrsKernelFactory*)" - - "_srs_app_factory - Application object factory (SrsAppFactory*)" - - app_globals: - - "_srs_server - Main SRS server instance (SrsServer*)" - - "_srs_sources - Live source manager (SrsLiveSourceManager*)" - - "_srs_stat - Statistics manager (SrsStatistic*)" - - "_srs_hooks - HTTP hooks manager (ISrsHttpHooks*)" - - "_srs_circuit_breaker - Circuit breaker for overload protection (ISrsCircuitBreaker*)" - - "_srs_dvr_async - Async worker for DVR operations (SrsAsyncCallWorker*)" - - timing_globals: - - "_srs_clock - Wall clock for time operations (SrsWallClock*)" - - "_srs_shared_timer - Shared timer for periodic tasks (SrsSharedTimer*)" - - "_srs_stages - Stage manager for pithy print (SrsStageManager*)" - - resource_globals: - - "_srs_conn_manager - Connection resource manager (SrsResourceManager*)" - - "_srs_pps_* - Various PPS (packets per second) statistics counters" - - webrtc_globals: - - "_srs_blackhole - WebRTC blackhole for packet dropping (SrsRtcBlackhole*)" - - "_srs_rtc_dtls_certificate - DTLS certificate for WebRTC (SrsDtlsCertificate*)" - - other_globals: - - "_srs_in_docker - Boolean flag indicating if running in Docker" - - "_srs_config_by_env - Boolean flag for environment variable configuration" - - "_srs_binary - Binary name of SRS executable" - - benefits: - - "Enables dependency injection for unit testing" - - "Allows mocking of global dependencies in tests" - - "Makes dependencies explicit and visible in class interface" - - "Improves code testability and maintainability" - - rationale: "Global variables create hidden dependencies that cannot be mocked or controlled in tests. Converting them to member fields makes dependencies explicit and testable." + principle: "MANDATORY - Store globals (_srs_*) as member fields for mockability" + pattern: | + Constructor: config_ = _srs_config; // Store reference + Destructor: config_ = NULL; // Set NULL, don't free + Methods: Use config_->method() instead of _srs_config->method() + common_globals: "_srs_config, _srs_sources, _srs_stat, _srs_hooks, _srs_context, _srs_log, _srs_server" cpp_compatibility: - standard: "C++98" - description: | - SRS codebase must be compatible with C++98 standard. Do NOT use C++11 or later features. - forbidden_features: - - "auto keyword for type deduction" - - "nullptr (use NULL instead)" - - "Range-based for loops (use traditional for loops)" - - "Lambda expressions" - - "std::unique_ptr, std::shared_ptr (use SRS custom smart pointers instead)" - - "Initializer lists" - - "decltype" - - "constexpr" - - "override and final keywords" - - "Strongly typed enums (enum class)" - - "Static assertions (static_assert)" - - "Variadic templates" - - "Rvalue references and move semantics" - allowed_patterns: - - "Traditional for loops: for (int i = 0; i < size; ++i)" - - "NULL instead of nullptr" - - "typedef instead of using for type aliases" - - "SRS custom smart pointers (SrsUniquePtr, SrsSharedPtr)" - - "Traditional function pointers instead of std::function" + standard: "C++98 only - NO C++11+ features" + forbidden: "auto, nullptr, range-for, lambda, std::unique_ptr, enum class, override, constexpr" + use: "NULL, traditional for-loops, typedef, SrsUniquePtr/SrsSharedPtr" memory_management: - - pattern: "SrsUniquePtr" - description: "Smart pointer for unique ownership - preferred for single ownership scenarios" - usage: "SrsUniquePtr ptr(new MyClass()); ptr->method(); // automatic cleanup" - - pattern: "SrsSharedPtr" - description: "Smart pointer for shared ownership - preferred for reference counting scenarios" - usage: "SrsSharedPtr ptr(new MyClass()); SrsSharedPtr copy = ptr; // reference counted" - - pattern: "srs_freep" - description: "Custom macro for freeing pointers - use only when smart pointers are not suitable" - - pattern: "srs_freepa" - description: "Custom macro for freeing arrays - use only when smart pointers are not suitable" + smart_pointers: "SrsUniquePtr (unique ownership), SrsSharedPtr (shared ownership)" + macros: "srs_freep (pointers), srs_freepa (arrays) - use only when smart pointers unsuitable" naming_conventions: - pattern: "field_naming" @@ -306,27 +76,7 @@ code_patterns: description: "MANDATORY - Always use SRS_DECLARE_PRIVATE instead of 'private' and SRS_DECLARE_PROTECTED instead of 'protected' in class definitions" purpose: "Enables unit tests to access private and protected members for dependency injection and mocking" - macro_definition: | - #ifdef SRS_FORCE_PUBLIC4UTEST - #define SRS_DECLARE_PRIVATE public - #define SRS_DECLARE_PROTECTED public - #else - #define SRS_DECLARE_PRIVATE private - #define SRS_DECLARE_PROTECTED protected - #endif - usage: | - WRONG: Using standard C++ access specifiers - class SrsBufferCache { - private: - ISrsAppConfig *config_; - ISrsRequest *req_; - protected: - virtual srs_error_t do_start(); - public: - srs_error_t start(); - }; - CORRECT: Using SRS access specifier macros class SrsBufferCache { SRS_DECLARE_PRIVATE: @@ -345,14 +95,6 @@ code_patterns: - "This applies to ALL production code classes in trunk/src/" - "When SRS_FORCE_PUBLIC4UTEST is defined, all private/protected members become public for testing" - benefits: - - "Enables unit tests to inject mock dependencies into private member fields" - - "Allows tests to access and verify internal state" - - "Makes classes fully testable without breaking encapsulation in production" - - "Provides clean separation between production and test builds" - - rationale: "This pattern allows unit tests to access private/protected members for dependency injection and mocking, while maintaining proper encapsulation in production builds. It's essential for writing comprehensive unit tests that can mock all dependencies." - commenting_style: - pattern: "C++ style comments" description: "MANDATORY - Use C++ style comments (//) instead of C style comments (/* */)" @@ -420,21 +162,6 @@ code_patterns: - "return srs_error_wrap(err, \"write audio format=%d\", format->codec);" - "return srs_error_wrap(err, \"connect to %s:%d\", host.c_str(), port);" - - pattern: "error_variable_declaration" - description: "Always declare srs_error_t err variable at function scope for error handling" - usage: | - CORRECT: Declare err variable at function start - srs_error_t MyClass::my_function() { - srs_error_t err = srs_success; - - // ... function implementation with error checking - if ((err = some_function()) != srs_success) { - return srs_error_wrap(err, "context"); - } - - return err; - } - binary_data_handling: - pattern: "SrsBuffer" description: "MANDATORY utility for marshaling and unmarshaling structs with bytes - ALWAYS use this instead of manual byte manipulation" @@ -527,71 +254,6 @@ code_patterns: - pattern: "#ifdef SRS_VALGRIND" description: "Valgrind support" - dependency_injection_for_testing: - - pattern: "assemble() method pattern" - description: "MANDATORY pattern for making classes testable by separating construction from initialization - applies ONLY to production code, NOT to unit test code or mock classes" - purpose: "Enables dependency injection for unit testing by deferring subscription/registration operations that depend on global state or external dependencies" - - usage: | - WRONG: Direct dependency in constructor (not testable) - class SrsOriginHub { - public: - SrsOriginHub() { - _srs_config->subscribe(this); // Hard dependency on global config - } - ~SrsOriginHub() { - _srs_config->unsubscribe(this); - } - }; - - CORRECT: Separate construction from initialization with assemble() - class SrsOriginHub { - private: - SrsConfig* config_; - public: - SrsOriginHub() { - config_ = _srs_config; // Store reference only - } - void assemble() { - config_->subscribe(this); // Actual initialization - } - ~SrsOriginHub() { - config_->unsubscribe(this); - config_ = NULL; - } - }; - - // Production code usage - hub_ = new SrsOriginHub(); - hub_->assemble(); // Call immediately after construction - - // Unit test usage - SrsOriginHub* hub = new SrsOriginHub(); - hub->config_ = mock_config; // Inject mock dependency - hub->assemble(); // Now uses mock config - // Or skip assemble() entirely to avoid side effects - - scope: "Applies ONLY to production code in trunk/src/app/, trunk/src/protocol/, trunk/src/kernel/, trunk/src/core/ - does NOT apply to unit test code or mock classes" - - when_to_use: - - "When constructor needs to invoke functions (e.g., subscribe(), register(), initialize() on dependencies)" - - "Exception: Simple object creation functions like srs_mutex_new() can remain in constructor - no need to extract to assemble()" - - "Rule: If constructor calls methods on dependencies, move those calls to assemble()" - - when_not_to_use: - - "In unit test code (trunk/src/utest/) - tests can use direct construction" - - "In mock classes (Mock* classes in utest files) - mocks are designed for testing" - - "For simple value initialization without external dependencies" - - "When the class is already easily testable without this pattern" - - benefits: - - "Enables dependency injection for unit testing" - - "Allows mocking of global dependencies" - - "Separates object construction from initialization side effects" - - "Makes code more testable without changing production behavior" - - rationale: "This pattern enables unit testing by allowing mock dependencies to be injected before initialization, while maintaining clean production code that calls assemble() immediately after construction. Unit test code and mock classes don't need this pattern because they are already designed for testing purposes." - codec_handling: enhanced_rtmp: description: | @@ -641,232 +303,7 @@ build_and_development: description: "Run all blackbox tests with verbose output and detailed SRS logs" working_directory: "trunk/3rdparty/srs-bench" - run_specific_test: - command: "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -test.run TestName" - description: "Run a specific test by name (e.g., TestFast_RtmpPublish_DvrFlv_Basic)" - examples: - - "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -test.run TestFast_RtmpPublish_DvrFlv_Basic" - - "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -test.run TestFast_RtmpPublish_RtmpPlay_Basic" - - "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -test.run 'TestFast_RtmpPublish_Dvr.*'" - - test_options: - - flag: "-test.v" - description: "Verbose output showing test progress (recommended)" - - flag: "-test.run " - description: "Run only tests matching the pattern (Go regex)" - - flag: "-srs-log" - description: "Enable detailed SRS log output (recommended for debugging)" - - flag: "-srs-stdout" - description: "Show SRS stdout logs (recommended for debugging)" - - flag: "-srs-ffmpeg-stderr" - description: "Show FFmpeg stderr logs" - - flag: "-srs-ffprobe-stdout" - description: "Show FFprobe stdout logs" - - flag: "-srs-binary " - description: "Specify custom SRS binary path (default: ../../objs/srs)" - - flag: "-srs-timeout " - description: "Timeout for each test case in milliseconds (default: 64000)" - - how_it_works: - - "Each blackbox test automatically starts a fresh SRS server instance" - - "SRS is configured via environment variables (e.g., SRS_VHOST_DVR_ENABLED=on)" - - "Tests use FFmpeg to publish streams and FFprobe to verify output" - - "SRS server is automatically stopped when test completes" - - "Each test runs in isolation with its own SRS instance and random ports" - - "No need to manually start or stop SRS server" - - test_categories: - rtmp: "TestFast_RtmpPublish_RtmpPlay_*, TestFast_RtmpPublish_HttpFlvPlay_*" - dvr: "TestFast_RtmpPublish_DvrFlv_*, TestFast_RtmpPublish_DvrMp4_*" - hls: "TestFast_RtmpPublish_HlsPlay_*" - hevc: "TestSlow_RtmpPublish_*_HEVC_*, TestSlow_SrtPublish_*_HEVC_*" - srt: "TestFast_SrtPublish_SrtPlay_*" - rtsp: "TestFast_RtmpPublish_RtspPlay_*" - http_api: "TestFast_Http_Api_*" - mp3: "TestFast_RtmpPublish_*_CodecMP3_*" - - common_workflows: - quick_test: - description: "Run a single fast test to verify basic functionality" - command: "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -test.run TestFast_RtmpPublish_RtmpPlay_Basic" - - dvr_tests: - description: "Run all DVR-related tests" - command: "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -test.run 'TestFast_RtmpPublish_Dvr.*'" - - debug_test: - description: "Run test with full logging including FFmpeg stderr for debugging" - command: "./objs/srs_blackbox_test -test.v -srs-log -srs-stdout -srs-ffmpeg-stderr -test.run TestName" - testing: - test_patterns: - - Note that private and protected members are accessible in utests, as there is a macro to convert them to public - - Use descriptive test names that clearly indicate what functionality is being tested - - Group related tests together (e.g., all tests for one class should be consecutive) - - Test both success and failure paths, especially for error handling scenarios - - Verify edge cases like sequence number wrap-around, cache overflow, and null inputs - - Use existing mock helper functions for consistency and maintainability - - mock_interface_fields: - principle: "MANDATORY - Always mock all private/protected interface member fields (ISrs* types) in the class under test" - description: | - When writing unit tests, ALWAYS identify and mock ALL interface member fields in the class under test. - Interface fields are those with ISrs* prefix (e.g., ISrsAppConfig*, ISrsBasicRtmpClient*, ISrsRequest*). - This enables proper dependency injection and isolation of the unit under test. - - process: - - "Step 1: View the class header file to identify all private/protected member fields" - - "Step 2: Identify which fields are interfaces (start with ISrs prefix)" - - "Step 3: Create or reuse mock classes for each interface type" - - "Step 4: In the test, inject mock instances into the class under test by setting the private fields directly" - - "Step 5: After test completes, set injected fields to NULL before object destruction to avoid double-free" - - example: | - // Class under test has these private fields: - class SrsEdgeRtmpUpstream { - private: - ISrsAppConfig *config_; // Interface - MUST mock - ISrsBasicRtmpClient *sdk_; // Interface - MUST mock - std::string redirect_; // Not interface - no need to mock - int selected_port_; // Not interface - no need to mock - }; - - // In unit test: - VOID TEST(EdgeRtmpUpstreamTest, ConnectToOrigin) { - // Create mocks for ALL interface fields - SrsUniquePtr mock_config(new MockEdgeConfig()); - MockEdgeRtmpClient *mock_sdk = new MockEdgeRtmpClient(); - - // Create object under test - SrsUniquePtr upstream(new SrsEdgeRtmpUpstream("")); - - // Inject mocks into private interface fields - upstream->config_ = mock_config.get(); - upstream->sdk_ = mock_sdk; - - // Run test - err = upstream->connect(req.get(), lb.get()); - HELPER_EXPECT_SUCCESS(err); - - // Verify mock interactions - EXPECT_TRUE(mock_sdk->connect_called_); - - // Clean up - set to NULL to avoid double-free - upstream->sdk_ = NULL; - srs_freep(mock_sdk); - } - - rules: - - "ALWAYS view the class header file first to identify all member fields" - - "ALWAYS mock ALL interface fields (ISrs* prefix) - no exceptions" - - "Create mock classes that implement the interface if they don't exist" - - "Reuse existing mock classes from srs_utest_app*.hpp when available" - - "Inject mocks by directly setting private member fields (accessible in utests)" - - "Set injected fields to NULL before object destruction to prevent double-free" - - "Non-interface fields (std::string, int, etc.) do not need mocking" - - rationale: "Mocking all interface dependencies ensures tests are isolated, fast, and don't require real network/file/database resources. It also makes tests deterministic and easier to debug." - - test_object_declaration: - - pattern: "Use unique pointers for object instantiation" - description: "MANDATORY - Always use SrsUniquePtr for object declaration in unit tests instead of stack allocation" - usage: | - WRONG: Stack allocation for SRS classes - SrsRtcPublishStream publish_stream(&mock_exec, &mock_expire, &mock_receiver, cid); - SrsBuffer buffer(data, size); - SrsHttpUri uri; - - CORRECT: Use SrsUniquePtr for SRS classes - SrsUniquePtr publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid)); - SrsUniquePtr buffer(new SrsBuffer(data, size)); - SrsUniquePtr uri(new SrsHttpUri()); - - // Access members using -> operator - HELPER_EXPECT_SUCCESS(publish_stream->initialize()); - buffer->write_1bytes(0xff); - uri->parse("http://example.com"); - - EXCEPTION: Mock objects should be declared directly (stack allocation) - MockRtcPacketReceiver mock_receiver; - MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1"); - MockRtcAsyncTaskExecutor mock_exec; - rationale: "Consistent with SRS memory management patterns, automatic cleanup, and prevents stack overflow issues with large objects. Mock objects are lightweight and designed for direct instantiation." - - mock_class_organization: - - pattern: "Mock class structure" - description: "MANDATORY - Always create mock class declarations in .hpp files and implementations in .cpp files" - usage: | - WRONG: Inline mock class definition in .cpp test file - // In srs_utest_app.cpp - class MockRtcConnection { - public: - bool enabled() { return true; } - srs_error_t send_packet() { return srs_success; } - }; - - CORRECT: Mock class declaration in .hpp, implementation in .cpp - // In srs_utest_app.hpp - class MockRtcConnection { - public: - bool enabled(); - srs_error_t send_packet(); - }; - - // In srs_utest_app.cpp - bool MockRtcConnection::enabled() { - return true; - } - srs_error_t MockRtcConnection::send_packet() { - return srs_success; - } - rationale: "Proper separation of interface and implementation, better code organization, and easier maintenance" - - - pattern: "Mock function organization" - description: "MANDATORY - Always declare mock functions in .hpp files and implement in .cpp files" - usage: | - WRONG: Inline mock function in .cpp test file - // In srs_utest_app.cpp - srs_error_t mock_read_function(char* buf, int size) { - return srs_success; - } - - CORRECT: Mock function declaration in .hpp, implementation in .cpp - // In srs_utest_app.hpp - srs_error_t mock_read_function(char* buf, int size); - - // In srs_utest_app.cpp - srs_error_t mock_read_function(char* buf, int size) { - return srs_success; - } - rationale: "Consistent with SRS coding standards and better code organization" - - - pattern: "Reuse existing mocks" - description: "MANDATORY - Always try to use existing mock classes by including the appropriate header file before creating new mocks" - usage: | - CORRECT: Check and reuse existing mocks - // First, include existing mock headers - #include "srs_utest_app.hpp" // For MockRtcConnection, MockHttpServer, etc. - - // Use existing mock if available - MockRtcConnection* mock_conn = new MockRtcConnection(); - - // Only create new mock if none exists - class MockNewFeature { - public: - srs_error_t new_method(); - }; - rationale: "Reduces code duplication, maintains consistency, and leverages existing test infrastructure" - - - pattern: "Mock creation guidelines" - description: "Guidelines for when and how to create new mock classes" - rules: - - "Only create new mock classes if no suitable existing mock is available" - - "Check all existing utest header files (srs_utest_app*.hpp) for reusable mocks" - - "Place new mock class declarations in the appropriate srs_utest_app*.hpp file" - - "Place new mock class implementations in the corresponding srs_utest_app*.cpp file" - - "Follow existing mock naming conventions (Mock prefix + class name)" - - "Keep mock implementations simple and focused on test requirements" - error_handling_macros: - Use HELPER_EXPECT_SUCCESS(x) when expecting a function to succeed (returns srs_success) - Use HELPER_EXPECT_FAILED(x) when expecting a function to fail (returns non-srs_success error) @@ -896,37 +333,3 @@ documentation: When looking for documentation or need to update docs, check this directory for markdown files and documentation structure. Do not search ossrs.io or ossrs.net for documentation, use the local files instead. - -faq: - rtmps_forward: - question: "Why doesn't SRS support RTMPS in the forward feature?" - answer: | - SRS SSL is designed for server-side only (accepting connections), not client-side (initiating connections). - Forward feature uses SrsSimpleRtmpClient which only supports plain RTMP protocol. - Solution: Use FFmpeg with SRS HTTP Hooks (on_publish/on_unpublish events) to automatically relay streams to RTMPS destinations. - - srt_missing_sps_pps: - question: "SRT streams missing SPS/PPS causing 'sps or pps empty' errors and black screen for players" - answer: | - When SRT streams are published without SPS/PPS (e.g., OBS with QSV/VAAPI encoder), SRS cannot generate video sequence headers. - The error is logged and ignored (connection stays alive), but no video frames are forwarded to RTMP/WebRTC. - Players connect successfully but see black screen (no video data). This is an encoder bug, not an SRS issue. - Solution: Use FFmpeg with QSV/VAAPI instead of OBS, or fix encoder settings to include SPS/PPS. - - webrtc_network_switch_rejoin: - question: "WebRTC multi-party call: Unable to re-enter room after network switching" - answer: | - When network switches occur (e.g., switching from intranet to external Wi-Fi), the server cannot detect the UDP connection - break immediately, leaving a stale session that blocks rejoining with the same display name. - Workarounds: Wait for session timeout (30-60 seconds), use unique display names with timestamps (e.g., "username_" + Date.now()), - or implement client-side retry logic with exponential backoff. - Why not fixed: Properly fixing this requires significant changes to WebRTC signaling protocol, heartbeat mechanisms, - and session management across server and all client SDKs. The complexity and maintenance cost is too high for this edge case. - - pcm_audio_not_supported: - question: "PCM audio codec not working in RTMP/FLV streaming" - answer: | - PCM audio codec is not supported in SRS. Only AAC, MP3, and Opus audio codecs are supported for RTMP/FLV streaming. - As of v7.0.102 (#4516), SRS explicitly returns an error for unsupported audio codecs instead of silently ignoring them. - Solution: Use FFmpeg to convert PCM audio to AAC: ffmpeg -i input -c:v copy -c:a aac -b:a 128k -f flv rtmp://server/live/stream - diff --git a/trunk/src/utest/srs_utest_ai04.cpp b/trunk/src/utest/srs_utest_ai04.cpp index 4bfc3ee24..db1b00d1b 100644 --- a/trunk/src/utest/srs_utest_ai04.cpp +++ b/trunk/src/utest/srs_utest_ai04.cpp @@ -211,19 +211,21 @@ VOID TEST(RTC3AudioCacheTest, PacketLossWithTimeout) // Should not process yet, waiting for missing packets EXPECT_EQ(0, (int)ready_packets.size()); - // Sleep for 10ms to exceed the 1ms timeout - srs_usleep(10 * SRS_UTIME_MILLISECONDS); - - // Process another packet to trigger timeout check - SrsUniquePtr pkt4(mock_create_audio_rtp_packet(104, 1080)); - HELPER_EXPECT_SUCCESS(cache.process_packet(pkt4.get(), ready_packets)); - - // Should process packet 103 despite missing 101, 102 due to timeout bool found_103 = false; - for (size_t i = 0; i < ready_packets.size(); i++) { - if (ready_packets[i]->header_.get_sequence() == 103) { - found_103 = true; - break; + for (int i = 0; i < 10 && !found_103; i++) { + // Sleep for N ms to exceed the 1ms timeout + srs_usleep(3 * SRS_UTIME_MILLISECONDS); + + // Process another packet to trigger timeout check + SrsUniquePtr pkt4(mock_create_audio_rtp_packet(104, 1080)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt4.get(), ready_packets)); + + // Should process packet 103 despite missing 101, 102 due to timeout + for (size_t i = 0; i < ready_packets.size(); i++) { + if (ready_packets[i]->header_.get_sequence() == 103) { + found_103 = true; + break; + } } } EXPECT_TRUE(found_103); diff --git a/trunk/src/utest/srs_utest_ai08.cpp b/trunk/src/utest/srs_utest_ai08.cpp index 606211f98..130381dae 100644 --- a/trunk/src/utest/srs_utest_ai08.cpp +++ b/trunk/src/utest/srs_utest_ai08.cpp @@ -3291,17 +3291,17 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioFormatConsumeError) SrsUniquePtr invalid_frame(new SrsMediaPacket()); invalid_frame->message_type_ = SrsFrameTypeAudio; - // Create invalid audio data (empty payload) + // Create invalid audio data with unsupported codec (Linear PCM, codec ID 0) char *invalid_data = new char[1]; - invalid_data[0] = 0x00; // Invalid audio format + invalid_data[0] = 0x00; // Linear PCM codec (ID=0) - unsupported invalid_frame->wrap(invalid_data, 1); invalid_frame->timestamp_ = 1000; // Test on_frame which calls on_audio internally - // This should call format_->on_audio(msg) but the format parsing should handle gracefully - // Note: SrsFormat::on_audio returns success for invalid data, just doesn't parse codec - HELPER_EXPECT_SUCCESS(builder.on_frame(invalid_frame.get())); + // This should call format_->on_audio(msg) which will return error for unsupported codec + // SrsFormat::on_audio now returns error for codecs that are not MP3/AAC/Opus + HELPER_EXPECT_FAILED(builder.on_frame(invalid_frame.get())); // Test with NULL payload data SrsUniquePtr null_frame(new SrsMediaPacket()); @@ -3334,7 +3334,7 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioNoAcodecParsed) HELPER_EXPECT_SUCCESS(builder.initialize(req.get())); // Test with audio data that has unknown/unsupported codec - // This will cause format_->on_audio() to succeed but not parse acodec_ + // This will cause format_->on_audio() to return error for unsupported codec SrsUniquePtr unknown_codec_frame(new SrsMediaPacket()); unknown_codec_frame->message_type_ = SrsFrameTypeAudio; @@ -3349,23 +3349,18 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioNoAcodecParsed) unknown_codec_frame->timestamp_ = 1000; // Test on_frame which calls on_audio internally - // This should call format_->on_audio(msg) successfully, but format_->acodec_ will be NULL - // because ADPCM is not supported, so the method should return early at line 1033-1035 - HELPER_EXPECT_SUCCESS(builder.on_frame(unknown_codec_frame.get())); + // This should call format_->on_audio(msg) which returns error for unsupported codec + // SrsFormat::on_audio now validates codec and returns error for non-MP3/AAC/Opus codecs + HELPER_EXPECT_FAILED(builder.on_frame(unknown_codec_frame.get())); - // Test with very short audio data that can't be parsed - SrsUniquePtr short_frame(new SrsMediaPacket()); - short_frame->message_type_ = SrsFrameTypeAudio; + // Test with NULL payload - this should succeed as format handles NULL gracefully + SrsUniquePtr null_frame(new SrsMediaPacket()); + null_frame->message_type_ = SrsFrameTypeAudio; + null_frame->timestamp_ = 2000; + // Don't wrap any data, payload will be NULL - // Create very short audio data (less than minimum required) - char *short_data = new char[1]; - short_data[0] = 0xFF; // Some data but too short to parse properly - - short_frame->wrap(short_data, 1); - short_frame->timestamp_ = 2000; - - // This should also result in format_->acodec_ being NULL after parsing - HELPER_EXPECT_SUCCESS(builder.on_frame(short_frame.get())); + // This should call format_->on_audio(msg) with NULL payload which returns success + HELPER_EXPECT_SUCCESS(builder.on_frame(null_frame.get())); // Verify that frames were processed successfully EXPECT_TRUE(true); // Basic test passed - no crash occurred @@ -3424,8 +3419,8 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioUnsupportedCodec) speex_frame->wrap(speex_data, 6); speex_frame->timestamp_ = 2000; - // This should also fail the codec validation since Speex is not AAC or MP3 - HELPER_EXPECT_SUCCESS(builder.on_frame(speex_frame.get())); + // This should fail the codec validation since Speex is not MP3/AAC/Opus + HELPER_EXPECT_FAILED(builder.on_frame(speex_frame.get())); // Test with Linear PCM codec (codec ID 0) SrsUniquePtr pcm_frame(new SrsMediaPacket()); @@ -3443,8 +3438,8 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioUnsupportedCodec) pcm_frame->wrap(pcm_data, 6); pcm_frame->timestamp_ = 3000; - // This should also fail the codec validation since Linear PCM is not AAC or MP3 - HELPER_EXPECT_SUCCESS(builder.on_frame(pcm_frame.get())); + // This should also fail the codec validation since Linear PCM is not MP3/AAC/Opus + HELPER_EXPECT_FAILED(builder.on_frame(pcm_frame.get())); // Verify that frames were processed successfully EXPECT_TRUE(true); // Basic test passed - no crash occurred @@ -3577,8 +3572,7 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioCodecValidationFailure) // This should succeed and set up format_->acodec_ HELPER_EXPECT_SUCCESS(builder.on_frame(aac_frame.get())); - // Now test with Linear PCM codec (ID=0) which is not supported by RTC builder - // but could theoretically be parsed (though format_ doesn't actually support it either) + // Now test with Linear PCM codec (ID=0) which is not supported by format SrsUniquePtr pcm_frame(new SrsMediaPacket()); pcm_frame->message_type_ = SrsFrameTypeAudio; @@ -3593,9 +3587,9 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioCodecValidationFailure) pcm_frame->wrap(pcm_data, 6); pcm_frame->timestamp_ = 2000; - // This should call format_->on_audio() which will not parse Linear PCM (returns early), - // so format_->acodec_ will remain NULL, covering the path at lines 1033-1035 - HELPER_EXPECT_SUCCESS(builder.on_frame(pcm_frame.get())); + // This should call format_->on_audio() which will return error for unsupported codec + // SrsFormat::on_audio validates codec and returns error for non-MP3/AAC/Opus codecs + HELPER_EXPECT_FAILED(builder.on_frame(pcm_frame.get())); // Verify that frames were processed successfully EXPECT_TRUE(true); // Test passed - covered codec validation paths @@ -3626,7 +3620,7 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioComprehensiveCoverage) // This covers the path where format_->on_audio() handles NULL/empty data gracefully HELPER_EXPECT_SUCCESS(builder.on_frame(empty_frame.get())); - // Test 2: Invalid codec data - covers format_->acodec_ being NULL after parsing + // Test 2: Invalid codec data - covers format_->on_audio() returning error for unsupported codec SrsUniquePtr invalid_frame(new SrsMediaPacket()); invalid_frame->message_type_ = SrsFrameTypeAudio; @@ -3637,8 +3631,8 @@ VOID TEST(StreamBridgeTest, SrsRtcRtpBuilder_OnAudioComprehensiveCoverage) invalid_frame->wrap(invalid_data, 2); invalid_frame->timestamp_ = 2000; - // This covers the path where format_->acodec_ is NULL (lines 1033-1035) - HELPER_EXPECT_SUCCESS(builder.on_frame(invalid_frame.get())); + // This covers the path where format_->on_audio() returns error for unsupported codec + HELPER_EXPECT_FAILED(builder.on_frame(invalid_frame.get())); // Test 3: OPUS codec - covers format_->on_audio() error path SrsUniquePtr opus_frame(new SrsMediaPacket()); diff --git a/trunk/src/utest/srs_utest_ai15.cpp b/trunk/src/utest/srs_utest_ai15.cpp index ec898f38f..d463f24f3 100644 --- a/trunk/src/utest/srs_utest_ai15.cpp +++ b/trunk/src/utest/srs_utest_ai15.cpp @@ -27,6 +27,22 @@ using namespace std; #include #include +// Mock PID file locker implementation for SrsServer::initialize() testing +MockPidFileLocker::MockPidFileLocker() +{ +} + +MockPidFileLocker::~MockPidFileLocker() +{ +} + +srs_error_t MockPidFileLocker::acquire() +{ + // Mock implementation that always succeeds without actually locking a file + // This allows tests to run even when a real SRS server is running + return srs_success; +} + // Mock config implementation for SrsServer::listen() testing MockAppConfigForServerListen::MockAppConfigForServerListen() { @@ -170,6 +186,11 @@ VOID TEST(SrsServerTest, InitializeSuccess) SrsUniquePtr server(new SrsServer()); EXPECT_TRUE(server.get() != NULL); + // Replace the PID file locker with a mock to avoid conflicts with running SRS server + SrsPidFileLocker *original_locker = server->pid_file_locker_; + MockPidFileLocker *mock_locker = new MockPidFileLocker(); + server->pid_file_locker_ = mock_locker; + // Call initialize() - this is the main test // This will initialize all server components in the correct sequence: // 1. PID file locker acquisition @@ -188,6 +209,10 @@ VOID TEST(SrsServerTest, InitializeSuccess) // The fact that initialize() returned success means all components // were initialized properly without errors EXPECT_TRUE(server.get() != NULL); + + // Restore original locker and cleanup mock + server->pid_file_locker_ = original_locker; + srs_freep(mock_locker); } // Test SrsServer::listen() method to verify proper listener setup for RTMP protocol. @@ -212,6 +237,11 @@ VOID TEST(SrsServerTest, ListenRtmpSuccess) // Inject mock config server->config_ = &mock_config; + // Replace the PID file locker with a mock to avoid conflicts with running SRS server + SrsPidFileLocker *original_locker = server->pid_file_locker_; + MockPidFileLocker *mock_locker = new MockPidFileLocker(); + server->pid_file_locker_ = mock_locker; + // Initialize server first (required before listen) HELPER_EXPECT_SUCCESS(server->initialize()); @@ -228,6 +258,10 @@ VOID TEST(SrsServerTest, ListenRtmpSuccess) // - Socket binding succeeded on the random port // - Connection manager started successfully EXPECT_TRUE(server.get() != NULL); + + // Restore original locker and cleanup mock + server->pid_file_locker_ = original_locker; + srs_freep(mock_locker); } // Test SrsServer::http_handle() method to verify proper HTTP API handler registration. diff --git a/trunk/src/utest/srs_utest_ai15.hpp b/trunk/src/utest/srs_utest_ai15.hpp index b450c5940..191aa6659 100644 --- a/trunk/src/utest/srs_utest_ai15.hpp +++ b/trunk/src/utest/srs_utest_ai15.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,17 @@ public: virtual std::string get_exporter_listen(); }; +// Mock PID file locker for testing SrsServer::initialize() +class MockPidFileLocker : public SrsPidFileLocker +{ +public: + MockPidFileLocker(); + virtual ~MockPidFileLocker(); + +public: + virtual srs_error_t acquire(); +}; + // Mock ISrsLog for testing SrsServer::on_signal() class MockLogForSignal : public ISrsLog { diff --git a/trunk/src/utest/srs_utest_ai22.cpp b/trunk/src/utest/srs_utest_ai22.cpp index e1bdc7d39..b2b2cd304 100644 --- a/trunk/src/utest/srs_utest_ai22.cpp +++ b/trunk/src/utest/srs_utest_ai22.cpp @@ -1049,7 +1049,7 @@ VOID TEST(EdgeIngesterTest, ProcessPublishMessageAudioVideo) srs_freep(ingester->upstream_); ingester->upstream_ = mock_upstream; - // Test 1: Process audio message + // Test 1: Process audio message with valid AAC codec { SrsUniquePtr audio_msg(new SrsRtmpCommonMessage()); audio_msg->header_.message_type_ = RTMP_MSG_AudioMessage; @@ -1057,6 +1057,14 @@ VOID TEST(EdgeIngesterTest, ProcessPublishMessageAudioVideo) audio_msg->header_.timestamp_ = 1000; audio_msg->create_payload(10); + // Set valid AAC audio data (codec ID 10) + char *payload = audio_msg->payload(); + payload[0] = 0xAF; // AAC codec (ID=10), 44kHz, 16-bit, stereo + payload[1] = 0x01; // AAC raw data (not sequence header) + for (int i = 2; i < 10; i++) { + payload[i] = 0x00; // Sample AAC data + } + std::string redirect; HELPER_EXPECT_SUCCESS(ingester->process_publish_message(audio_msg.get(), redirect)); } diff --git a/trunk/src/utest/srs_utest_st.cpp b/trunk/src/utest/srs_utest_st.cpp index e213cdbfe..34dc5790b 100644 --- a/trunk/src/utest/srs_utest_st.cpp +++ b/trunk/src/utest/srs_utest_st.cpp @@ -83,8 +83,13 @@ VOID TEST(StTest, StUtimePerformance) EXPECT_GE(gettimeofday_elapsed_time, 0); EXPECT_GE(st_utime_elapsed_time, 0); - // pass the test, if - EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ? gettimeofday_elapsed_time - st_utime_elapsed_time : st_utime_elapsed_time - gettimeofday_elapsed_time, 30); + // Calculate absolute difference between the two elapsed times + int time_diff = gettimeofday_elapsed_time > st_utime_elapsed_time + ? gettimeofday_elapsed_time - st_utime_elapsed_time + : st_utime_elapsed_time - gettimeofday_elapsed_time; + + // The difference should be less than N clock ticks (microseconds) + EXPECT_LT(time_diff, 100); } // check gettimeofday first, then st_utime @@ -104,7 +109,13 @@ VOID TEST(StTest, StUtimePerformance) EXPECT_GE(gettimeofday_elapsed_time, 0); EXPECT_GE(st_utime_elapsed_time, 0); - EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ? gettimeofday_elapsed_time - st_utime_elapsed_time : st_utime_elapsed_time - gettimeofday_elapsed_time, 30); + // Calculate absolute difference between the two elapsed times + int time_diff = gettimeofday_elapsed_time > st_utime_elapsed_time + ? gettimeofday_elapsed_time - st_utime_elapsed_time + : st_utime_elapsed_time - gettimeofday_elapsed_time; + + // The difference should be less than N clock ticks (microseconds) + EXPECT_LT(time_diff, 100); } // compare st_utime & gettimeofday in a loop @@ -126,7 +137,13 @@ VOID TEST(StTest, StUtimePerformance) EXPECT_GE(gettimeofday_elapsed_time, 0); EXPECT_GE(st_utime_elapsed_time, 0); - EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ? gettimeofday_elapsed_time - st_utime_elapsed_time : st_utime_elapsed_time - gettimeofday_elapsed_time, 100); + // Calculate absolute difference between the two elapsed times + int time_diff = gettimeofday_elapsed_time > st_utime_elapsed_time + ? gettimeofday_elapsed_time - st_utime_elapsed_time + : st_utime_elapsed_time - gettimeofday_elapsed_time; + + // The difference should be less than N clock ticks (microseconds) + EXPECT_LT(time_diff, 100); } }