// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/command_line.h" #include "base/debug/trace_event_impl.h" #include "base/json/json_reader.h" #include "base/strings/stringprintf.h" #include "base/test/trace_event_analyzer.h" #include "base/values.h" #include "content/browser/media/webrtc_internals.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "content/test/webrtc_content_browsertest_base.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/perf/perf_test.h" #if defined(OS_WIN) #include "base/win/windows_version.h" #endif using trace_analyzer::TraceAnalyzer; using trace_analyzer::Query; using trace_analyzer::TraceEventVector; namespace { static const char kGetUserMediaAndStop[] = "getUserMediaAndStop"; static const char kGetUserMediaAndGetStreamUp[] = "getUserMediaAndGetStreamUp"; static const char kGetUserMediaAndAnalyseAndStop[] = "getUserMediaAndAnalyseAndStop"; static const char kGetUserMediaAndExpectFailure[] = "getUserMediaAndExpectFailure"; static const char kRenderSameTrackMediastreamAndStop[] = "renderSameTrackMediastreamAndStop"; static const char kRenderClonedMediastreamAndStop[] = "renderClonedMediastreamAndStop"; static const char kRenderClonedTrackMediastreamAndStop[] = "renderClonedTrackMediastreamAndStop"; static const char kRenderDuplicatedMediastreamAndStop[] = "renderDuplicatedMediastreamAndStop"; // Results returned by JS. static const char kOK[] = "OK"; std::string GenerateGetUserMediaWithMandatorySourceID( const std::string& function_name, const std::string& audio_source_id, const std::string& video_source_id) { const std::string audio_constraint = "audio: {mandatory: { sourceId:\"" + audio_source_id + "\"}}, "; const std::string video_constraint = "video: {mandatory: { sourceId:\"" + video_source_id + "\"}}"; return function_name + "({" + audio_constraint + video_constraint + "});"; } std::string GenerateGetUserMediaWithOptionalSourceID( const std::string& function_name, const std::string& audio_source_id, const std::string& video_source_id) { const std::string audio_constraint = "audio: {optional: [{sourceId:\"" + audio_source_id + "\"}]}, "; const std::string video_constraint = "video: {optional: [{ sourceId:\"" + video_source_id + "\"}]}"; return function_name + "({" + audio_constraint + video_constraint + "});"; } } // namespace namespace content { class WebRtcGetUserMediaBrowserTest: public WebRtcContentBrowserTest, public testing::WithParamInterface { public: WebRtcGetUserMediaBrowserTest() : trace_log_(NULL) {} virtual ~WebRtcGetUserMediaBrowserTest() {} virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { WebRtcContentBrowserTest::SetUpCommandLine(command_line); bool enable_audio_track_processing = GetParam(); if (enable_audio_track_processing) command_line->AppendSwitch(switches::kEnableAudioTrackProcessing); } void StartTracing() { CHECK(trace_log_ == NULL) << "Can only can start tracing once"; trace_log_ = base::debug::TraceLog::GetInstance(); trace_log_->SetEnabled(base::debug::CategoryFilter("video"), base::debug::TraceLog::RECORDING_MODE, base::debug::TraceLog::ENABLE_SAMPLING); // Check that we are indeed recording. EXPECT_EQ(trace_log_->GetNumTracesRecorded(), 1); } void StopTracing() { CHECK(message_loop_runner_ == NULL) << "Calling StopTracing more than once"; trace_log_->SetDisabled(); message_loop_runner_ = new MessageLoopRunner; trace_log_->Flush(base::Bind( &WebRtcGetUserMediaBrowserTest::OnTraceDataCollected, base::Unretained(this))); message_loop_runner_->Run(); } void OnTraceDataCollected( const scoped_refptr& events_str_ptr, bool has_more_events) { CHECK(!has_more_events); recorded_trace_data_ = events_str_ptr; message_loop_runner_->Quit(); } TraceAnalyzer* CreateTraceAnalyzer() { return TraceAnalyzer::Create("[" + recorded_trace_data_->data() + "]"); } void RunGetUserMediaAndCollectMeasures(const int time_to_sample_secs, const std::string& measure_filter, const std::string& graph_name) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); // Put getUserMedia to work and let it run for a couple of seconds. DCHECK(time_to_sample_secs); ExecuteJavascriptAndWaitForOk( base::StringPrintf("%s({video: true});", kGetUserMediaAndGetStreamUp)); // Now the stream is up and running, start collecting traces. StartTracing(); // Let the stream run for a while in javascript. ExecuteJavascriptAndWaitForOk( base::StringPrintf("waitAndStopVideoTrack(%d);", time_to_sample_secs)); // Wait until the page title changes to "OK". Do not sleep() here since that // would stop both this code and the browser underneath. StopTracing(); scoped_ptr analyzer(CreateTraceAnalyzer()); analyzer->AssociateBeginEndEvents(); trace_analyzer::TraceEventVector events; DCHECK(measure_filter.size()); analyzer->FindEvents( Query::EventNameIs(measure_filter), &events); ASSERT_GT(events.size(), 0u) << "Could not collect any samples during test, this is bad"; std::string duration_us; std::string interarrival_us; for (size_t i = 0; i != events.size(); ++i) { duration_us.append( base::StringPrintf("%d,", static_cast(events[i]->duration))); } for (size_t i = 1; i < events.size(); ++i) { // The event |timestamp| comes in ns, divide to get us like |duration|. interarrival_us.append(base::StringPrintf("%d,", static_cast((events[i]->timestamp - events[i - 1]->timestamp) / base::Time::kNanosecondsPerMicrosecond))); } perf_test::PrintResultList( graph_name, "", "sample_duration", duration_us, "us", true); perf_test::PrintResultList( graph_name, "", "interarrival_time", interarrival_us, "us", true); } void GetSources(std::vector* audio_ids, std::vector* video_ids) { GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); std::string sources_as_json = ExecuteJavascriptAndReturnResult( "getSources()"); EXPECT_FALSE(sources_as_json.empty()); int error_code; std::string error_message; scoped_ptr value( base::JSONReader::ReadAndReturnError(sources_as_json, base::JSON_ALLOW_TRAILING_COMMAS, &error_code, &error_message)); ASSERT_TRUE(value.get() != NULL) << error_message; EXPECT_EQ(value->GetType(), base::Value::TYPE_LIST); base::ListValue* values; ASSERT_TRUE(value->GetAsList(&values)); for (base::ListValue::iterator it = values->begin(); it != values->end(); ++it) { const base::DictionaryValue* dict; std::string kind; std::string id; ASSERT_TRUE((*it)->GetAsDictionary(&dict)); ASSERT_TRUE(dict->GetString("kind", &kind)); ASSERT_TRUE(dict->GetString("id", &id)); ASSERT_FALSE(id.empty()); EXPECT_TRUE(kind == "audio" || kind == "video"); if (kind == "audio") { audio_ids->push_back(id); } else if (kind == "video") { video_ids->push_back(id); } } ASSERT_FALSE(audio_ids->empty()); ASSERT_FALSE(video_ids->empty()); } private: base::debug::TraceLog* trace_log_; scoped_refptr recorded_trace_data_; scoped_refptr message_loop_runner_; }; static const bool kRunTestsWithFlag[] = { false, true }; INSTANTIATE_TEST_CASE_P(WebRtcGetUserMediaBrowserTests, WebRtcGetUserMediaBrowserTest, testing::ValuesIn(kRunTestsWithFlag)); // These tests will all make a getUserMedia call with different constraints and // see that the success callback is called. If the error callback is called or // none of the callbacks are called the tests will simply time out and fail. IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetVideoStreamAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk( base::StringPrintf("%s({video: true});", kGetUserMediaAndStop)); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, RenderSameTrackMediastreamAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk( base::StringPrintf("%s({video: true});", kRenderSameTrackMediastreamAndStop)); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, RenderClonedMediastreamAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk( base::StringPrintf("%s({video: true});", kRenderClonedMediastreamAndStop)); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, kRenderClonedTrackMediastreamAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk( base::StringPrintf("%s({video: true});", kRenderClonedTrackMediastreamAndStop)); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, kRenderDuplicatedMediastreamAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk( base::StringPrintf("%s({video: true});", kRenderDuplicatedMediastreamAndStop)); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetAudioAndVideoStreamAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk(base::StringPrintf( "%s({video: true, audio: true});", kGetUserMediaAndStop)); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetAudioAndVideoStreamAndClone) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk("getUserMediaAndClone();"); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, RenderVideoTrackInMultipleTagsAndPause) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk("getUserMediaAndRenderInSeveralVideoTags();"); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetUserMediaWithMandatorySourceID) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); std::vector audio_ids; std::vector video_ids; GetSources(&audio_ids, &video_ids); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); // Test all combinations of mandatory sourceID; for (std::vector::const_iterator video_it = video_ids.begin(); video_it != video_ids.end(); ++video_it) { for (std::vector::const_iterator audio_it = audio_ids.begin(); audio_it != audio_ids.end(); ++audio_it) { NavigateToURL(shell(), url); EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithMandatorySourceID( kGetUserMediaAndStop, *audio_it, *video_it))); } } } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetUserMediaWithInvalidMandatorySourceID) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); std::vector audio_ids; std::vector video_ids; GetSources(&audio_ids, &video_ids); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); // Test with invalid mandatory audio sourceID. NavigateToURL(shell(), url); EXPECT_EQ("DevicesNotFoundError", ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithMandatorySourceID( kGetUserMediaAndExpectFailure, "something invalid", video_ids[0]))); // Test with invalid mandatory video sourceID. EXPECT_EQ("DevicesNotFoundError", ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithMandatorySourceID( kGetUserMediaAndExpectFailure, audio_ids[0], "something invalid"))); // Test with empty mandatory audio sourceID. EXPECT_EQ("DevicesNotFoundError", ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithMandatorySourceID( kGetUserMediaAndExpectFailure, "", video_ids[0]))); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetUserMediaWithInvalidOptionalSourceID) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); std::vector audio_ids; std::vector video_ids; GetSources(&audio_ids, &video_ids); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); // Test with invalid optional audio sourceID. NavigateToURL(shell(), url); EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithOptionalSourceID( kGetUserMediaAndStop, "something invalid", video_ids[0]))); // Test with invalid optional video sourceID. EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithOptionalSourceID( kGetUserMediaAndStop, audio_ids[0], "something invalid"))); // Test with empty optional audio sourceID. EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult( GenerateGetUserMediaWithOptionalSourceID( kGetUserMediaAndStop, "", video_ids[0]))); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, TwoGetUserMediaAndStop) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); NavigateToURL(shell(), url); ExecuteJavascriptAndWaitForOk( "twoGetUserMediaAndStop({video: true, audio: true});"); } IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetUserMediaWithTooHighVideoConstraintsValues) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL url(embedded_test_server()->GetURL("/media/getusermedia.html")); int large_value = 99999; std::string call = GenerateGetUserMediaCall(kGetUserMediaAndExpectFailure, large_value, large_value, large_value, large_value, large_value, large_value); NavigateToURL(shell(), url); // TODO(perkj): A proper error code should be returned by gUM. EXPECT_EQ("TrackStartError", ExecuteJavascriptAndReturnResult(call)); } // This test will make a simple getUserMedia page, verify that video is playing // in a simple local