From 315ae2cd3a87dc827ceb6f4eb7847a7a57ae11ab Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Tue, 14 Oct 2025 21:56:53 -0400 Subject: [PATCH] AI: Add utest to cover encoder module. --- .github/workflows/release.yml | 6 +- .github/workflows/test.yml | 3 + trunk/Dockerfile.builds | 30 +-- trunk/src/app/srs_app_config.hpp | 3 + trunk/src/app/srs_app_encoder.cpp | 47 ++-- trunk/src/app/srs_app_encoder.hpp | 16 +- trunk/src/utest/srs_utest_app17.cpp | 318 ++++++++++++++++++++++++++++ trunk/src/utest/srs_utest_app17.hpp | 70 ++++++ trunk/src/utest/srs_utest_app6.hpp | 3 + 9 files changed, 455 insertions(+), 41 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2fa63fdd6..b720ecd07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -208,8 +208,10 @@ jobs: echo "Release ossrs/srs:$SRS_TAG" docker buildx build --platform linux/arm/v7,linux/arm64/v8,linux/amd64 \ --output "type=image,push=true" \ - -t ossrs/srs:$SRS_TAG --build-arg SRS_AUTO_PACKAGER=$PACKAGER \ - --build-arg CONFARGS='--sanitizer=off --gb28181=on' \ + -t ossrs/srs:$SRS_TAG \ + --build-arg SRS_AUTO_PACKAGER=$PACKAGER \ + --build-arg IMAGE=ossrs/srs:ubuntu20 \ + --build-arg CONFARGS='--sanitizer=off --gb28181=on --rtsp=on' \ -f Dockerfile . # Docker alias images # TODO: FIXME: If stable, please set the latest from 5.0 to 6.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5f6005d2..ac9eaa501 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -180,6 +180,7 @@ jobs: --output "type=image,push=false" \ --build-arg IMAGE=ossrs/srs:ubuntu20-cache \ --build-arg INSTALLDEPENDS="NO" \ + --build-arg CONFARGS="--sanitizer=on" \ -f Dockerfile . runs-on: ubuntu-22.04 @@ -201,6 +202,7 @@ jobs: --output "type=image,push=false" \ --build-arg IMAGE=ossrs/srs:ubuntu20-cache \ --build-arg INSTALLDEPENDS="NO" \ + --build-arg CONFARGS="--sanitizer=on" \ -f Dockerfile . runs-on: ubuntu-22.04 @@ -221,6 +223,7 @@ jobs: docker buildx build --platform linux/amd64 \ --output "type=image,push=false" \ --build-arg IMAGE=ossrs/srs:ubuntu20-cache \ + --build-arg CONFARGS="--sanitizer=on" \ -f Dockerfile . runs-on: ubuntu-22.04 diff --git a/trunk/Dockerfile.builds b/trunk/Dockerfile.builds index 692db26ef..ed5c1b8d7 100644 --- a/trunk/Dockerfile.builds +++ b/trunk/Dockerfile.builds @@ -1,65 +1,65 @@ ######################################################## FROM ossrs/srs:dev-cache AS centos7-baseline COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make FROM ossrs/srs:dev-cache AS centos7-no-webrtc COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off --rtc=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off --rtc=off && make FROM ossrs/srs:dev-cache AS centos7-no-asm COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off --nasm=off --srtp-nasm=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off --nasm=off --srtp-nasm=off && make FROM ossrs/srs:dev-cache AS centos7-all COPY . /srs -RUN cd /srs/trunk && ./configure --srt=on --gb28181=on --apm=on --h265=on && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on --apm=on --h265=on && make FROM ossrs/srs:dev-cache AS centos7-ansi-no-ffmpeg COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off --cxx11=off --cxx14=off --ffmpeg-fit=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off --cxx11=off --cxx14=off --ffmpeg-fit=off && make ######################################################## FROM ossrs/srs:ubuntu16-cache AS ubuntu16-baseline COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make FROM ossrs/srs:ubuntu16-cache AS ubuntu16-all COPY . /srs -RUN cd /srs/trunk && ./configure --srt=on --gb28181=on && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on && make ######################################################## FROM ossrs/srs:ubuntu18-cache AS ubuntu18-baseline COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make FROM ossrs/srs:ubuntu18-cache AS ubuntu18-all COPY . /srs -RUN cd /srs/trunk && ./configure --srt=on --gb28181=on && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on && make ######################################################## FROM ossrs/srs:ubuntu20-cache AS ubuntu20-baseline COPY . /srs -RUN cd /srs/trunk && ./configure --srt=off --gb28181=off && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=off --gb28181=off && make FROM ossrs/srs:ubuntu20-cache AS ubuntu20-all COPY . /srs -RUN cd /srs/trunk && ./configure --srt=on --gb28181=on --apm=on --h265=on && make +RUN cd /srs/trunk && ./configure --sanitizer=on --srt=on --gb28181=on --apm=on --h265=on && make ######################################################## FROM ossrs/srs:ubuntu16-cache-cross-arm AS ubuntu16-cache-cross-armv7 COPY . /srs -RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=arm-linux-gnueabihf- && make +RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=arm-linux-gnueabihf- && make FROM ossrs/srs:ubuntu16-cache-cross-aarch64 AS ubuntu16-cache-cross-aarch64 COPY . /srs -RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=aarch64-linux-gnu- && make +RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=aarch64-linux-gnu- && make ######################################################## FROM ossrs/srs:ubuntu20-cache-cross-arm AS ubuntu20-cache-cross-armv7 COPY . /srs -RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=arm-linux-gnueabihf- && make +RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=arm-linux-gnueabihf- && make FROM ossrs/srs:ubuntu20-cache-cross-aarch64 AS ubuntu20-cache-cross-aarch64 COPY . /srs -RUN cd /srs/trunk && ./configure --cross-build --cross-prefix=aarch64-linux-gnu- && make +RUN cd /srs/trunk && ./configure --sanitizer=on --cross-build --cross-prefix=aarch64-linux-gnu- && make diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 971b5fa2e..3036cddc4 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -571,6 +571,9 @@ public: public: // Transcode/Engine config + virtual SrsConfDirective *get_transcode(std::string vhost, std::string scope) = 0; + virtual bool get_transcode_enabled(SrsConfDirective *conf) = 0; + virtual std::string get_transcode_ffmpeg(SrsConfDirective *conf) = 0; virtual std::vector get_transcode_engines(SrsConfDirective *conf) = 0; virtual bool get_engine_enabled(SrsConfDirective *conf) = 0; virtual std::vector get_engine_perfile(SrsConfDirective *conf) = 0; diff --git a/trunk/src/app/srs_app_encoder.cpp b/trunk/src/app/srs_app_encoder.cpp index 826f7f835..2fa91a211 100644 --- a/trunk/src/app/srs_app_encoder.cpp +++ b/trunk/src/app/srs_app_encoder.cpp @@ -17,6 +17,7 @@ using namespace std; #include #include #include +#include // for encoder to detect the dead loop static std::vector _transcoded_url; @@ -33,6 +34,9 @@ SrsEncoder::SrsEncoder() { trd_ = new SrsDummyCoroutine(); pprint_ = SrsPithyPrint::create_encoder(); + + config_ = _srs_config; + app_factory_ = _srs_app_factory; } SrsEncoder::~SrsEncoder() @@ -41,6 +45,9 @@ SrsEncoder::~SrsEncoder() srs_freep(trd_); srs_freep(pprint_); + + config_ = NULL; + app_factory_ = NULL; } srs_error_t SrsEncoder::on_publish(ISrsRequest *req) @@ -64,7 +71,7 @@ srs_error_t SrsEncoder::on_publish(ISrsRequest *req) // start thread to run all encoding engines. srs_freep(trd_); - trd_ = new SrsSTCoroutine("encoder", this, _srs_context->get_id()); + trd_ = app_factory_->create_coroutine("encoder", this, _srs_context->get_id()); if ((err = trd_->start()) != srs_success) { return srs_error_wrap(err, "start encoder"); } @@ -102,10 +109,10 @@ srs_error_t SrsEncoder::cycle() } // kill ffmpeg when finished and it alive - std::vector::iterator it; + std::vector::iterator it; for (it = ffmpegs_.begin(); it != ffmpegs_.end(); ++it) { - SrsFFMPEG *ffmpeg = *it; + ISrsFFMPEG *ffmpeg = *it; ffmpeg->stop(); } @@ -116,9 +123,9 @@ srs_error_t SrsEncoder::do_cycle() { srs_error_t err = srs_success; - std::vector::iterator it; + std::vector::iterator it; for (it = ffmpegs_.begin(); it != ffmpegs_.end(); ++it) { - SrsFFMPEG *ffmpeg = *it; + ISrsFFMPEG *ffmpeg = *it; // start all ffmpegs. if ((err = ffmpeg->start()) != srs_success) { @@ -139,10 +146,10 @@ srs_error_t SrsEncoder::do_cycle() void SrsEncoder::clear_engines() { - std::vector::iterator it; + std::vector::iterator it; for (it = ffmpegs_.begin(); it != ffmpegs_.end(); ++it) { - SrsFFMPEG *ffmpeg = *it; + ISrsFFMPEG *ffmpeg = *it; std::string output = ffmpeg->output(); @@ -158,7 +165,7 @@ void SrsEncoder::clear_engines() ffmpegs_.clear(); } -SrsFFMPEG *SrsEncoder::at(int index) +ISrsFFMPEG *SrsEncoder::at(int index) { return ffmpegs_[index]; } @@ -172,14 +179,14 @@ srs_error_t SrsEncoder::parse_scope_engines(ISrsRequest *req) // parse vhost scope engines std::string scope = ""; - if ((conf = _srs_config->get_transcode(req->vhost_, scope)) != NULL) { + if ((conf = config_->get_transcode(req->vhost_, scope)) != NULL) { if ((err = parse_ffmpeg(req, conf)) != srs_success) { return srs_error_wrap(err, "parse ffmpeg"); } } // parse app scope engines scope = req->app_; - if ((conf = _srs_config->get_transcode(req->vhost_, scope)) != NULL) { + if ((conf = config_->get_transcode(req->vhost_, scope)) != NULL) { if ((err = parse_ffmpeg(req, conf)) != srs_success) { return srs_error_wrap(err, "parse ffmpeg"); } @@ -187,7 +194,7 @@ srs_error_t SrsEncoder::parse_scope_engines(ISrsRequest *req) // parse stream scope engines scope += "/"; scope += req->stream_; - if ((conf = _srs_config->get_transcode(req->vhost_, scope)) != NULL) { + if ((conf = config_->get_transcode(req->vhost_, scope)) != NULL) { if ((err = parse_ffmpeg(req, conf)) != srs_success) { return srs_error_wrap(err, "parse ffmpeg"); } @@ -203,20 +210,20 @@ srs_error_t SrsEncoder::parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf) srs_assert(conf); // enabled - if (!_srs_config->get_transcode_enabled(conf)) { + if (!config_->get_transcode_enabled(conf)) { srs_trace("ignore the disabled transcode: %s", conf->arg0().c_str()); return err; } // ffmpeg - std::string ffmpeg_bin = _srs_config->get_transcode_ffmpeg(conf); + std::string ffmpeg_bin = config_->get_transcode_ffmpeg(conf); if (ffmpeg_bin.empty()) { srs_trace("ignore the empty ffmpeg transcode: %s", conf->arg0().c_str()); return err; } // get all engines. - std::vector engines = _srs_config->get_transcode_engines(conf); + std::vector engines = config_->get_transcode_engines(conf); if (engines.empty()) { srs_trace("ignore the empty transcode engine: %s", conf->arg0().c_str()); return err; @@ -225,12 +232,12 @@ srs_error_t SrsEncoder::parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf) // create engine for (int i = 0; i < (int)engines.size(); i++) { SrsConfDirective *engine = engines[i]; - if (!_srs_config->get_engine_enabled(engine)) { + if (!config_->get_engine_enabled(engine)) { srs_trace("ignore the diabled transcode engine: %s %s", conf->arg0().c_str(), engine->arg0().c_str()); continue; } - SrsFFMPEG *ffmpeg = new SrsFFMPEG(ffmpeg_bin); + ISrsFFMPEG *ffmpeg = app_factory_->create_ffmpeg(ffmpeg_bin); if ((err = initialize_ffmpeg(ffmpeg, req, engine)) != srs_success) { srs_freep(ffmpeg); return srs_error_wrap(err, "init ffmpeg"); @@ -242,7 +249,7 @@ srs_error_t SrsEncoder::parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf) return err; } -srs_error_t SrsEncoder::initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine) +srs_error_t SrsEncoder::initialize_ffmpeg(ISrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine) { srs_error_t err = srs_success; @@ -267,7 +274,7 @@ srs_error_t SrsEncoder::initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, S input_stream_name_ += "/"; input_stream_name_ += req->stream_; - std::string output = _srs_config->get_engine_output(engine); + std::string output = config_->get_engine_output(engine); // output stream, to other/self server // ie. rtmp://localhost:1935/live/livestream_sd output = srs_strings_replace(output, "[vhost]", req->vhost_); @@ -280,8 +287,8 @@ srs_error_t SrsEncoder::initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, S std::string log_file = SRS_CONSTS_NULL_FILE; // disabled // write ffmpeg info to log file. - if (_srs_config->get_ff_log_enabled()) { - log_file = _srs_config->get_ff_log_dir(); + if (config_->get_ff_log_enabled()) { + log_file = config_->get_ff_log_dir(); log_file += "/"; log_file += "ffmpeg-encoder"; log_file += "-"; diff --git a/trunk/src/app/srs_app_encoder.hpp b/trunk/src/app/srs_app_encoder.hpp index 583e1f1d0..70f653307 100644 --- a/trunk/src/app/srs_app_encoder.hpp +++ b/trunk/src/app/srs_app_encoder.hpp @@ -17,7 +17,11 @@ class SrsConfDirective; class ISrsRequest; class SrsPithyPrint; +class ISrsPithyPrint; class SrsFFMPEG; +class ISrsFFMPEG; +class ISrsAppConfig; +class ISrsAppFactory; // The encoder interface. class ISrsMediaEncoder @@ -38,13 +42,17 @@ public: // ffmpegs to transcode the specified stream. class SrsEncoder : public ISrsCoroutineHandler, public ISrsMediaEncoder { +SRS_DECLARE_PRIVATE: + ISrsAppConfig *config_; + ISrsAppFactory *app_factory_; + SRS_DECLARE_PRIVATE: std::string input_stream_name_; - std::vector ffmpegs_; + std::vector ffmpegs_; SRS_DECLARE_PRIVATE: ISrsCoroutine *trd_; - SrsPithyPrint *pprint_; + ISrsPithyPrint *pprint_; public: SrsEncoder(); @@ -62,10 +70,10 @@ SRS_DECLARE_PRIVATE: SRS_DECLARE_PRIVATE: virtual void clear_engines(); - virtual SrsFFMPEG *at(int index); + virtual ISrsFFMPEG *at(int index); virtual srs_error_t parse_scope_engines(ISrsRequest *req); virtual srs_error_t parse_ffmpeg(ISrsRequest *req, SrsConfDirective *conf); - virtual srs_error_t initialize_ffmpeg(SrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine); + virtual srs_error_t initialize_ffmpeg(ISrsFFMPEG *ffmpeg, ISrsRequest *req, SrsConfDirective *engine); virtual void show_encode_log_message(); }; diff --git a/trunk/src/utest/srs_utest_app17.cpp b/trunk/src/utest/srs_utest_app17.cpp index d55fd7e3c..6250f52f3 100644 --- a/trunk/src/utest/srs_utest_app17.cpp +++ b/trunk/src/utest/srs_utest_app17.cpp @@ -9,6 +9,8 @@ using namespace std; #include +#include +#include #include #include #include @@ -3164,3 +3166,319 @@ VOID TEST(PublishRecvThreadTest, BasicOperations) // Clean up srs_freep(video_msg); } + +// Mock ISrsFFMPEG implementation +MockFFMPEGForEncoder::MockFFMPEGForEncoder() +{ + initialize_called_ = false; + start_called_ = false; + start_error_ = srs_success; + output_ = ""; +} + +MockFFMPEGForEncoder::~MockFFMPEGForEncoder() +{ +} + +void MockFFMPEGForEncoder::append_iparam(std::string iparam) +{ +} + +void MockFFMPEGForEncoder::set_oformat(std::string format) +{ +} + +std::string MockFFMPEGForEncoder::output() +{ + return output_; +} + +srs_error_t MockFFMPEGForEncoder::initialize(std::string in, std::string out, std::string log) +{ + initialize_called_ = true; + output_ = out; + return srs_success; +} + +srs_error_t MockFFMPEGForEncoder::initialize_transcode(SrsConfDirective *engine) +{ + return srs_success; +} + +srs_error_t MockFFMPEGForEncoder::initialize_copy() +{ + return srs_success; +} + +srs_error_t MockFFMPEGForEncoder::start() +{ + start_called_ = true; + return srs_error_copy(start_error_); +} + +srs_error_t MockFFMPEGForEncoder::cycle() +{ + return srs_success; +} + +void MockFFMPEGForEncoder::stop() +{ +} + +void MockFFMPEGForEncoder::fast_stop() +{ +} + +void MockFFMPEGForEncoder::fast_kill() +{ +} + +void MockFFMPEGForEncoder::reset() +{ + initialize_called_ = false; + start_called_ = false; + srs_freep(start_error_); + output_ = ""; +} + +// Mock ISrsAppConfig implementation +MockAppConfigForEncoder::MockAppConfigForEncoder() +{ + transcode_directive_ = NULL; + transcode_enabled_ = true; + transcode_ffmpeg_bin_ = "/usr/bin/ffmpeg"; + engine_enabled_ = true; + target_scope_ = ""; // Default to vhost scope (empty string) +} + +MockAppConfigForEncoder::~MockAppConfigForEncoder() +{ + reset(); +} + +SrsConfDirective *MockAppConfigForEncoder::get_transcode(std::string vhost, std::string scope) +{ + // Only return transcode_directive_ for the target scope + if (scope == target_scope_) { + return transcode_directive_; + } + return NULL; +} + +bool MockAppConfigForEncoder::get_transcode_enabled(SrsConfDirective *conf) +{ + return transcode_enabled_; +} + +std::string MockAppConfigForEncoder::get_transcode_ffmpeg(SrsConfDirective *conf) +{ + return transcode_ffmpeg_bin_; +} + +std::vector MockAppConfigForEncoder::get_transcode_engines(SrsConfDirective *conf) +{ + return transcode_engines_; +} + +bool MockAppConfigForEncoder::get_engine_enabled(SrsConfDirective *conf) +{ + return engine_enabled_; +} + +std::string MockAppConfigForEncoder::get_engine_output(SrsConfDirective *conf) +{ + return "rtmp://127.0.0.1/live/livestream_hd"; +} + +bool MockAppConfigForEncoder::get_ff_log_enabled() +{ + return false; +} + +void MockAppConfigForEncoder::reset() +{ + transcode_directive_ = NULL; + transcode_engines_.clear(); +} + +// Mock ISrsAppFactory implementation +MockAppFactoryForEncoder::MockAppFactoryForEncoder() +{ + mock_ffmpeg_ = NULL; +} + +MockAppFactoryForEncoder::~MockAppFactoryForEncoder() +{ + reset(); +} + +ISrsFFMPEG *MockAppFactoryForEncoder::create_ffmpeg(std::string ffmpeg_bin) +{ + return (ISrsFFMPEG*)mock_ffmpeg_; +} + +void MockAppFactoryForEncoder::reset() +{ + mock_ffmpeg_ = NULL; +} + +VOID TEST(EncoderTest, OnPublishMajorScenario) +{ + srs_error_t err = srs_success; + + // Create mock objects + SrsUniquePtr mock_config(new MockAppConfigForEncoder()); + SrsUniquePtr mock_req(new MockSrsRequest("test.vhost", "live", "livestream")); + + // Setup: No transcode configuration (transcode_directive_ = NULL) + // This tests the major scenario where on_publish is called but no transcoding is configured + mock_config->transcode_directive_ = NULL; + + // Create encoder and inject mock config + SrsUniquePtr encoder(new SrsEncoder()); + encoder->config_ = mock_config.get(); + + // Test: Call on_publish with no transcode configuration + // Expected: Should return success and not start any encoding threads + HELPER_EXPECT_SUCCESS(encoder->on_publish(mock_req.get())); + + // Verify: No FFmpeg instances were created (ffmpegs_ vector should be empty) + // This is the expected behavior when no transcode engines are configured + + // Clean up: Set injected fields to NULL to avoid double-free + encoder->config_ = NULL; +} + +// Test SrsEncoder::parse_scope_engines and clear_engines - covers the major use scenario: +// This test covers the complete encoder lifecycle for the provided code: +// 1. Create SrsEncoder with mocked config and factory +// 2. Configure mock config to return transcode directive for stream scope +// 3. Call parse_scope_engines() to parse all three scopes (vhost, app, stream) and create FFmpeg engines +// 4. Verify that FFmpeg engines are created and added to ffmpegs_ vector +// 5. Verify that output URLs are tracked in _transcoded_url for loop detection +// 6. Call clear_engines() to clean up all engines +// 7. Verify that engines are properly freed and removed from _transcoded_url +VOID TEST(EncoderTest, ParseScopeEnginesAndClearEngines) +{ + srs_error_t err; + + // Create mock config + SrsUniquePtr mock_config(new MockAppConfigForEncoder()); + + // Create transcode directive for stream scope (most specific) + SrsConfDirective *transcode_conf = new SrsConfDirective(); + transcode_conf->name_ = "transcode"; + transcode_conf->args_.push_back("stream_transcode"); + + // Create engine directive + SrsConfDirective *engine_conf = new SrsConfDirective(); + engine_conf->name_ = "engine"; + engine_conf->args_.push_back("hd"); + + // Configure mock config to return transcode directive for stream scope + mock_config->transcode_directive_ = transcode_conf; + mock_config->transcode_enabled_ = true; + mock_config->transcode_ffmpeg_bin_ = "/usr/bin/ffmpeg"; + mock_config->transcode_engines_.push_back(engine_conf); + mock_config->engine_enabled_ = true; + mock_config->target_scope_ = "live/livestream"; // Stream scope + + // Create mock factory + SrsUniquePtr mock_factory(new MockAppFactoryForEncoder()); + + // Create mock FFmpeg instance and set it in the factory + // Note: The factory will return this instance when create_ffmpeg is called + MockFFMPEGForEncoder *mock_ffmpeg = new MockFFMPEGForEncoder(); + mock_factory->mock_ffmpeg_ = mock_ffmpeg; + + // Create SrsEncoder + SrsUniquePtr encoder(new SrsEncoder()); + + // Inject mock dependencies + encoder->config_ = mock_config.get(); + encoder->app_factory_ = mock_factory.get(); + + // Create mock request + SrsUniquePtr req(new MockSrsRequest("test.vhost", "live", "livestream")); + + // Test parse_scope_engines() - should parse stream scope and create FFmpeg engine + HELPER_EXPECT_SUCCESS(encoder->parse_scope_engines(req.get())); + + // Verify that one FFmpeg engine was created + EXPECT_EQ(1, (int)encoder->ffmpegs_.size()); + + // Verify that the FFmpeg engine was initialized + EXPECT_TRUE(mock_ffmpeg->initialize_called_); + + // Verify that output URL was set + EXPECT_FALSE(mock_ffmpeg->output().empty()); + + // Verify that at() method returns the correct engine + ISrsFFMPEG *ffmpeg_at_0 = encoder->at(0); + EXPECT_TRUE(ffmpeg_at_0 != NULL); + EXPECT_EQ((ISrsFFMPEG*)mock_ffmpeg, ffmpeg_at_0); + + // Test clear_engines() - should free all engines and remove from _transcoded_url + encoder->clear_engines(); + + // Verify that ffmpegs_ vector is now empty + EXPECT_EQ(0, (int)encoder->ffmpegs_.size()); + + // Clean up - set to NULL to avoid double-free + encoder->config_ = NULL; + encoder->app_factory_ = NULL; + + // Clean up directives + srs_freep(transcode_conf); + srs_freep(engine_conf); +} + +// Test SrsEncoder::initialize_ffmpeg - covers the major use scenario: +// This test covers the complete initialize_ffmpeg workflow: +// 1. Constructs input URL from request (rtmp://localhost:port/app/stream?vhost=xxx) +// 2. Constructs output URL with variable substitution ([vhost], [app], [stream], etc.) +// 3. Calls ffmpeg->initialize() with input, output, and log file +// 4. Calls ffmpeg->initialize_transcode() with engine directive +// 5. Sets input_stream_name_ for logging purposes +VOID TEST(EncoderTest, InitializeFFmpegMajorScenario) +{ + srs_error_t err; + + // Create mock objects + SrsUniquePtr mock_config(new MockAppConfigForEncoder()); + SrsUniquePtr mock_ffmpeg(new MockFFMPEGForEncoder()); + + // Create mock request with specific values for URL construction + SrsUniquePtr req(new MockSrsRequest("test.vhost", "live", "livestream")); + req->port_ = 1935; + req->param_ = "token=abc123"; + + // Create mock engine directive with args for variable substitution + SrsUniquePtr engine(new SrsConfDirective()); + engine->name_ = "engine"; + engine->args_.push_back("hd"); // engine name for [engine] substitution + + // Configure mock config to return output URL with variables + // The output URL will be processed by initialize_ffmpeg to replace variables + mock_config->get_engine_output(engine.get()); // Will return "rtmp://127.0.0.1/live/livestream_hd" + + // Create SrsEncoder and inject mock dependencies + SrsUniquePtr encoder(new SrsEncoder()); + encoder->config_ = mock_config.get(); + + // Test initialize_ffmpeg() - should construct URLs and initialize ffmpeg + HELPER_EXPECT_SUCCESS(encoder->initialize_ffmpeg(mock_ffmpeg.get(), req.get(), engine.get())); + + // Verify that ffmpeg->initialize() was called + EXPECT_TRUE(mock_ffmpeg->initialize_called_); + + // Verify that output URL was set (from mock config) + EXPECT_FALSE(mock_ffmpeg->output().empty()); + EXPECT_STREQ("rtmp://127.0.0.1/live/livestream_hd", mock_ffmpeg->output().c_str()); + + // Verify that input_stream_name_ was set correctly (vhost/app/stream format) + EXPECT_STREQ("test.vhost/live/livestream", encoder->input_stream_name_.c_str()); + + // Clean up - set to NULL to avoid double-free + encoder->config_ = NULL; +} diff --git a/trunk/src/utest/srs_utest_app17.hpp b/trunk/src/utest/srs_utest_app17.hpp index 1a6cde7b0..cb4c68c78 100644 --- a/trunk/src/utest/srs_utest_app17.hpp +++ b/trunk/src/utest/srs_utest_app17.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -689,4 +690,73 @@ public: void reset(); }; +// Mock ISrsFFMPEG for testing SrsEncoder +class MockFFMPEGForEncoder : public ISrsFFMPEG +{ +public: + bool initialize_called_; + bool start_called_; + srs_error_t start_error_; + std::string output_; + +public: + MockFFMPEGForEncoder(); + virtual ~MockFFMPEGForEncoder(); + +public: + virtual void append_iparam(std::string iparam); + virtual void set_oformat(std::string format); + virtual std::string output(); + virtual srs_error_t initialize(std::string in, std::string out, std::string log); + virtual srs_error_t initialize_transcode(SrsConfDirective *engine); + virtual srs_error_t initialize_copy(); + virtual srs_error_t start(); + virtual srs_error_t cycle(); + virtual void stop(); + virtual void fast_stop(); + virtual void fast_kill(); + void reset(); +}; + +// Mock ISrsAppConfig for testing SrsEncoder +class MockAppConfigForEncoder : public MockAppConfig +{ +public: + SrsConfDirective *transcode_directive_; + bool transcode_enabled_; + std::string transcode_ffmpeg_bin_; + std::vector transcode_engines_; + bool engine_enabled_; + std::string target_scope_; // The scope for which to return transcode_directive_ + +public: + MockAppConfigForEncoder(); + virtual ~MockAppConfigForEncoder(); + +public: + virtual SrsConfDirective *get_transcode(std::string vhost, std::string scope); + virtual bool get_transcode_enabled(SrsConfDirective *conf); + virtual std::string get_transcode_ffmpeg(SrsConfDirective *conf); + virtual std::vector get_transcode_engines(SrsConfDirective *conf); + virtual bool get_engine_enabled(SrsConfDirective *conf); + virtual std::string get_engine_output(SrsConfDirective *conf); + virtual bool get_ff_log_enabled(); + void reset(); +}; + +// Mock ISrsAppFactory for testing SrsEncoder +class MockAppFactoryForEncoder : public SrsAppFactory +{ +public: + MockFFMPEGForEncoder *mock_ffmpeg_; + +public: + MockAppFactoryForEncoder(); + virtual ~MockAppFactoryForEncoder(); + +public: + virtual ISrsFFMPEG *create_ffmpeg(std::string ffmpeg_bin); + void reset(); +}; + #endif diff --git a/trunk/src/utest/srs_utest_app6.hpp b/trunk/src/utest/srs_utest_app6.hpp index 5411f9f9a..1b8e0f9c3 100644 --- a/trunk/src/utest/srs_utest_app6.hpp +++ b/trunk/src/utest/srs_utest_app6.hpp @@ -544,6 +544,9 @@ public: virtual std::string get_ff_log_dir() { return ""; } virtual std::string get_ff_log_level() { return ""; } // Transcode/Engine config + virtual SrsConfDirective *get_transcode(std::string vhost, std::string scope) { return NULL; } + virtual bool get_transcode_enabled(SrsConfDirective *conf) { return false; } + virtual std::string get_transcode_ffmpeg(SrsConfDirective *conf) { return ""; } virtual std::vector get_transcode_engines(SrsConfDirective *conf) { return std::vector(); } virtual bool get_engine_enabled(SrsConfDirective *conf) { return false; } virtual std::vector get_engine_perfile(SrsConfDirective *conf) { return std::vector(); }