summaryrefslogtreecommitdiffstats
path: root/remoting/test/chromoting_test_driver.cc
blob: e8c977fd737adcf8aaf813c0408e4e5b2fcea44b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Copyright 2015 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/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/test_suite.h"
#include "base/test/test_switches.h"
#include "google_apis/google_api_keys.h"
#include "net/base/escape.h"
#include "remoting/test/chromoting_test_driver_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace switches {
const char kAuthCodeSwitchName[] = "authcode";
const char kHelpSwitchName[] = "help";
const char kHostNameSwitchName[] = "hostname";
const char kHostJidSwitchName[] = "hostjid";
const char kLoggingLevelSwitchName[] = "verbosity";
const char kPinSwitchName[] = "pin";
const char kRefreshTokenPathSwitchName[] = "refresh-token-path";
const char kSingleProcessTestsSwitchName[] = "single-process-tests";
const char kUserNameSwitchName[] = "username";
}

namespace {
const char kChromotingAuthScopeValues[] =
    "https://www.googleapis.com/auth/chromoting "
    "https://www.googleapis.com/auth/googletalk "
    "https://www.googleapis.com/auth/userinfo.email";

std::string GetAuthorizationCodeUri() {
  // Replace space characters with a '+' sign when formatting.
  bool use_plus = true;
  return base::StringPrintf(
      "https://accounts.google.com/o/oauth2/auth"
      "?scope=%s"
      "&redirect_uri=https://chromoting-oauth.talkgadget.google.com/"
      "talkgadget/oauth/chrome-remote-desktop/dev"
      "&response_type=code"
      "&client_id=%s"
      "&access_type=offline"
      "&approval_prompt=force",
      net::EscapeUrlEncodedData(kChromotingAuthScopeValues, use_plus).c_str(),
      net::EscapeUrlEncodedData(
          google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING),
          use_plus).c_str());
}

void PrintUsage() {
  printf("\n************************************\n");
  printf("*** Chromoting Test Driver Usage ***\n");
  printf("************************************\n");

  printf("\nUsage:\n");
  printf("  chromoting_test_driver --username=<example@gmail.com> [options]"
         " --hostname=<example hostname>\n");
  printf("\nRequired Parameters:\n");
  printf("  %s: Specifies which account to use when running tests\n",
         switches::kUserNameSwitchName);
  printf("  %s: Specifies which host to connect to when running tests\n",
         switches::kHostNameSwitchName);
  printf("\nOptional Parameters:\n");
  printf("  %s: Exchanged for a refresh and access token for authentication\n",
         switches::kAuthCodeSwitchName);
  printf("  %s: Displays additional usage information\n",
         switches::kHelpSwitchName);
  printf("  %s: Path to a JSON file containing username/refresh_token KVPs\n",
         switches::kRefreshTokenPathSwitchName);
  printf("  %s: Specifies the optional logging level of the tool (0-3)."
         " [default: off]\n", switches::kLoggingLevelSwitchName);
}

void PrintAuthCodeInfo() {
  printf("\n*******************************\n");
  printf("*** Auth Code Example Usage ***\n");
  printf("*******************************\n\n");

  printf("If this is the first time you are running the tool,\n");
  printf("you will need to provide an authorization code.\n");
  printf("This code will be exchanged for a long term refresh token which\n");
  printf("will be stored locally and used to acquire a short lived access\n");
  printf("token to connect to the remoting service apis and establish a\n");
  printf("remote host connection.\n\n");

  printf("Note: You may need to repeat this step if the stored refresh token");
  printf("\n      has been revoked or expired.\n");
  printf("      Passing in the same auth code twice will result in an error\n");

  printf("\nFollow these steps to produce an auth code:\n"
         " - Open the Authorization URL link shown below in your browser\n"
         " - Approve the requested permissions for the tool\n"
         " - Copy the 'code' value in the redirected URL\n"
         " - Run the tool and pass in copied auth code as a parameter\n");

  printf("\nAuthorization URL:\n");
  printf("%s\n", GetAuthorizationCodeUri().c_str());

  printf("\nRedirected URL Example:\n");
  printf("https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/"
         "chrome-remote-desktop/dev?code=4/AKtf...\n");

  printf("\nTool usage example with the newly created auth code:\n");
  printf("chromoting_test_driver --%s=example@gmail.com --%s=example_host_name"
         " --%s=4/AKtf...\n\n",
         switches::kUserNameSwitchName,
         switches::kHostNameSwitchName,
         switches::kAuthCodeSwitchName);
}

void PrintJsonFileInfo() {
  printf("\n****************************************\n");
  printf("*** Refresh Token File Example Usage ***\n");
  printf("****************************************\n\n");

  printf("In order to use this option, a valid JSON file must exist, be\n");
  printf("properly formatted, and contain a username/token KVP.\n");
  printf("Contents of example_file.json\n");
  printf("{\n");
  printf("  \"username1@fauxdomain.com\": \"1/3798Gsdf898shksdvfyi8sshad\",\n");
  printf("  \"username2@fauxdomain.com\": \"1/8974sdf87asdgadfgaerhfRsAa\",\n");
  printf("}\n\n");

  printf("\nTool usage example:\n");
  printf("chromoting_test_driver --%s=%s --%s=example_host_name"
         " --%s=./example_file.json\n\n",
         switches::kUserNameSwitchName, "username1@fauxdomain.com",
         switches::kHostNameSwitchName, switches::kRefreshTokenPathSwitchName);
}

}  // namespace

int main(int argc, char* argv[]) {
  base::TestSuite test_suite(argc, argv);
  base::MessageLoopForIO message_loop;

  if (!base::CommandLine::InitializedForCurrentProcess()) {
    if (!base::CommandLine::Init(argc, argv)) {
      LOG(ERROR) << "Failed to initialize command line singleton.";
      return -1;
    }
  }

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  DCHECK(command_line);

  // Do not retry if tests fails.
  command_line->AppendSwitchASCII(switches::kTestLauncherRetryLimit, "0");

  // Different tests may require access to the same host if run in parallel.
  // To avoid shared resource contention, tests will be run one at a time.
  command_line->AppendSwitch(switches::kSingleProcessTestsSwitchName);

  // If the user passed in the help flag, then show the help info for this tool
  // and 'run' the tests which will print the gtest specific help and then exit.
  // NOTE: We do this check after updating the switches as otherwise the gtest
  //       help is written in parallel with our text and can appear interleaved.
  if (command_line->HasSwitch(switches::kHelpSwitchName)) {
    PrintUsage();
    PrintJsonFileInfo();
    PrintAuthCodeInfo();
    return base::LaunchUnitTestsSerially(
        argc, argv,
        base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
  }

  // Update the logging verbosity level if user specified one.
  std::string verbosity_level(
      command_line->GetSwitchValueASCII(switches::kLoggingLevelSwitchName));
  if (!verbosity_level.empty()) {
    // Turn on logging for the test_driver and remoting components.
    // This switch is parsed during logging::InitLogging.
    command_line->AppendSwitchASCII("vmodule",
                                    "*/remoting/*=" + verbosity_level);
    logging::LoggingSettings logging_settings;
    logging::InitLogging(logging_settings);
  }

  remoting::test::ChromotingTestDriverEnvironment::EnvironmentOptions options;

  options.user_name =
      command_line->GetSwitchValueASCII(switches::kUserNameSwitchName);
  if (options.user_name.empty()) {
    LOG(ERROR) << "No username passed in, can't authenticate or run tests!";
    return -1;
  }
  VLOG(1) << "Running chromoting tests as: " << options.user_name;

  // Check to see if the user passed in a one time use auth_code for
  // refreshing their credentials.
  std::string auth_code =
      command_line->GetSwitchValueASCII(switches::kAuthCodeSwitchName);
  options.refresh_token_file_path =
     command_line->GetSwitchValuePath(switches::kRefreshTokenPathSwitchName);

  // The host name determines which host to initiate a session with from the
  // host list returned from the directory service.
  options.host_name =
      command_line->GetSwitchValueASCII(switches::kHostNameSwitchName);

  if (options.host_name.empty()) {
    LOG(ERROR) << "No hostname passed in, connect to host requires hostname!";
    return -1;
  }

  options.host_jid =
      command_line->GetSwitchValueASCII(switches::kHostJidSwitchName);

  VLOG(1) << "Chromoting tests will connect to: " << options.host_name;

  options.pin = command_line->GetSwitchValueASCII(switches::kPinSwitchName);

  // Create and register our global test data object. It will handle
  // retrieving an access token or host list for the user. The GTest framework
  // will own the lifetime of this object once it is registered below.
  scoped_ptr<remoting::test::ChromotingTestDriverEnvironment> shared_data(
      new remoting::test::ChromotingTestDriverEnvironment(options));

  if (!shared_data->Initialize(auth_code)) {
    // If we failed to initialize our shared data object, then bail.
    return -1;
  }

  if (!options.host_jid.empty() &&
      !shared_data->WaitForHostOnline(options.host_jid, options.host_name)) {
    // Host with expected JID is not online. No point running further tests.
    return -1;
  }

  // Since we've successfully set up our shared_data object, we'll assign the
  // value to our global* and transfer ownership to the framework.
  remoting::test::g_chromoting_shared_data = shared_data.release();
  testing::AddGlobalTestEnvironment(remoting::test::g_chromoting_shared_data);

  // Running the tests serially will avoid clients from connecting to the same
  // host.
  return base::LaunchUnitTestsSerially(
      argc, argv,
      base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
}