summaryrefslogtreecommitdiffstats
path: root/remoting/test/app_remoting_test_driver.cc
blob: 0d8af2b3a73c2cf14fc7465019edb85a750e7ddb (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
// 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/logging.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/app_remoting_test_driver_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace switches {
const char kAuthCodeSwitchName[] = "authcode";
const char kHelpSwitchName[] = "help";
const char kLoggingLevelSwitchName[] = "verbosity";
const char kServiceEnvironmentSwitchName[] = "environment";
const char kShowHostAvailabilitySwitchName[] = "show-host-availability";
const char kSingleProcessTestsSwitchName[] = "single-process-tests";
const char kUserNameSwitchName[] = "username";
}

namespace {

// Requested permissions needed for App Remoting tests.  The spaces in between
// scope fragments are necessary and will be escaped properly before use.
const char kAppRemotingAuthScopeValues[] =
    "https://www.googleapis.com/auth/appremoting.runapplication"
    " https://www.googleapis.com/auth/googletalk"
    " https://www.googleapis.com/auth/userinfo.email"
    " https://docs.google.com/feeds"
    " https://www.googleapis.com/auth/drive";

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(kAppRemotingAuthScopeValues, use_plus).c_str(),
      net::EscapeUrlEncodedData(
          google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING),
          use_plus).c_str());
}

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

  printf("\nUsage:\n");
  printf("  ar_test_driver --username=<example@gmail.com> [options]\n");
  printf("\nRequired Parameters:\n");
  printf("  %s: Specifies which account to use when running tests\n",
         switches::kUserNameSwitchName);
  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: Specifies the service api to use (dev|test) [default: dev]\n",
         switches::kServiceEnvironmentSwitchName);
  printf(
      "  %s: Retrieves and displays the connection status for all known "
      "hosts, no tests will be run\n",
      switches::kShowHostAvailabilitySwitchName);
  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("ar_test_driver --%s=example@gmail.com --%s=4/AKtf...\n\n",
         switches::kUserNameSwitchName, switches::kAuthCodeSwitchName);
}

}  // namespace

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  base::TestSuite test_suite(argc, argv);

  // The pointer returned here refers to a singleton, since we don't own the
  // lifetime of the object, don't wrap in a scoped_ptr construct or release it.
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  DCHECK(command_line);

  // We do not want to retry failures as a failed test should signify an error
  // to be investigated.
  command_line->AppendSwitchASCII(switches::kTestLauncherRetryLimit, "0");

  // We do not want to run the tests in parallel and we do not want to retry
  // failures.  The reason for running in a single process is that some tests
  // may share the same remoting host and they cannot be run concurrently, also
  // the test output gets spammed with test launcher messages which reduces the
  // readability of the results.
  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();
    PrintAuthCodeInfo();
    return base::LaunchUnitTestsSerially(
        argc, argv,
        base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
  }

  // Verify we received the required input from the command line.
  if (!command_line->HasSwitch(switches::kUserNameSwitchName)) {
    LOG(ERROR) << "No user name passed in, can't authenticate without that!";
    PrintUsage();
    return -1;
  }

  std::string user_name;
  user_name = command_line->GetSwitchValueASCII(switches::kUserNameSwitchName);
  DVLOG(1) << "Running tests as: " << user_name;

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

  // If the user passed in a service environment, use it, otherwise set a
  // default value.
  remoting::test::ServiceEnvironment service_environment;
  std::string service_environment_switch = command_line->GetSwitchValueASCII(
      switches::kServiceEnvironmentSwitchName);
  if (service_environment_switch.empty() ||
      service_environment_switch == "dev") {
    service_environment =
        remoting::test::ServiceEnvironment::kDeveloperEnvironment;
  } else if (service_environment_switch == "test") {
    service_environment =
        remoting::test::ServiceEnvironment::kTestingEnvironment;
  } else {
    LOG(ERROR) << "Invalid " << switches::kServiceEnvironmentSwitchName
               << " argument passed in.";
    PrintUsage();
    return -1;
  }

  // Update the logging verbosity level is user specified one.
  std::string verbosity_level;
  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);
  }

  // Create and register our global test data object.  It will handle
  // retrieving an access token for the user and spinning up VMs.
  // The GTest framework will own the lifetime of this object once
  // it is registered below.
  scoped_ptr<remoting::test::AppRemotingTestDriverEnvironment> shared_data;

  shared_data.reset(new remoting::test::AppRemotingTestDriverEnvironment(
      user_name, service_environment));

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

  if (command_line->HasSwitch(switches::kShowHostAvailabilitySwitchName)) {
    // When this flag is specified, we will retrieve connection information
    // for all known applications and report the status.  No tests will be run.
    shared_data->ShowHostAvailability();
    return 0;
  }

  // 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::AppRemotingSharedData = shared_data.release();
  testing::AddGlobalTestEnvironment(remoting::test::AppRemotingSharedData);

  // Because many tests may access the same remoting host(s), we need to run
  // the tests sequentially so they do not interfere with each other.
  return base::LaunchUnitTestsSerially(
      argc, argv,
      base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
}