Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
Program.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Net;
8 using System.Net.NetworkInformation;
9 using System.Net.Sockets;
10 using System.Reflection;
11 using System.Text;
12 using System.Threading;
13 using Mono.Options;
14 using SiliconStudio.Paradox.Graphics.Regression;
15 
16 namespace SiliconStudio.Paradox.TestRunner2
17 {
19  {
20  private const char IpAddressesSplitCharacter = '%';
21 
22  /// <summary>
23  /// The name of the branch the test is done on;
24  /// </summary>
25  private string branchName;
26 
27  /// <summary>
28  /// The address of the server.
29  /// </summary>
30  private string serverAddresses;
31 
32  /// <summary>
33  /// The current buildNumber.
34  /// </summary>
35  private int buildNumber = -1;
36 
37  /// <summary>
38  /// The server used to get results from the test.
39  /// </summary>
40  private TcpListener server;
41 
42  public TestServerHost(int bn, string branch)
43  {
44  buildNumber = bn;
45  branchName = branch;
46  }
47 
48  int FindAvailablePort(int startRange, int endRange)
49  {
50  // Tries up to 100 times (in case port are used)
51  for (int i = 0; i < 100; ++i)
52  {
53  // Evaluate TCP connections
54  var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
55  var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();
56 
57  // Try each port
58  for (int port = startRange; port <= endRange; ++port)
59  {
60  bool isAvailable = true;
61 
62  foreach (TcpConnectionInformation tcpi in tcpConnInfoArray)
63  {
64  if (tcpi.LocalEndPoint.Port == port)
65  {
66  isAvailable = false;
67  break;
68  }
69  }
70 
71  if (isAvailable)
72  return port;
73  }
74 
75  // Wait little bit and try again
76  Thread.Sleep(1);
77  }
78 
79  throw new InvalidOperationException(string.Format("Could not find a valid port in the range {0}-{1}", startRange, endRange));
80  }
81 
82  /// <summary>
83  /// Create the server and start it.
84  /// </summary>
85  TcpListener StartServer()
86  {
87  //TODO: IPv6 ?
88  var nics = NetworkInterface.GetAllNetworkInterfaces();
89 
90  serverAddresses = "";
91 
92  // List network interfaces, with the ones having a gateway first
93  foreach (var ip in nics.Select(x => x.GetIPProperties()).OrderBy(x => x.GatewayAddresses.Count > 0 ? 0 : 1))
94  {
95  foreach (var addr in ip.UnicastAddresses)
96  {
97  if (addr.Address.AddressFamily == AddressFamily.InterNetwork && !String.IsNullOrEmpty(addr.Address.ToString()) && !addr.Address.ToString().Equals(@"127.0.0.1"))
98  serverAddresses = String.Join(IpAddressesSplitCharacter.ToString(), serverAddresses, addr.Address);
99  }
100  }
101 
102  if (serverAddresses.Equals(""))
103  {
104  Console.WriteLine(@"No IP address found.");
105  return null;
106  }
107 
108  var serverPort = FindAvailablePort(20000, 20100);
109  var server = new TcpListener(IPAddress.Any, serverPort);
110  Console.WriteLine(@"Server listening to port {0}", serverPort);
111  server.Start();
112 
113  return server;
114  }
115 
116  public int RunAndroidTest(ConnectedDevice device, bool reinstall, string packageName, string packageFile, string resultFile)
117  {
118  try
119  {
120  server = StartServer();
121 
122  if (reinstall)
123  {
124  // force stop - only works for Android 3.0 and above.
125  var o0 = ShellHelper.RunProcessAndGetOutput(@"adb", string.Format(@"-s {0} shell am force-stop {1}", device.Serial, packageName));
126 
127  // uninstall
128  ShellHelper.RunProcessAndGetOutput(@"adb", string.Format(@"-s {0} uninstall {1}", device.Serial, packageName));
129 
130  // install
131  var o1 = ShellHelper.RunProcessAndGetOutput(@"adb", string.Format(@"-s {0} install {1}", device.Serial, packageFile));
132  Console.WriteLine("adb install: exitcode {0}\nOutput: {1}\nErrors: {2}", o1.ExitCode, string.Join(Environment.NewLine, o1.OutputLines), string.Join(Environment.NewLine, o1.OutputErrors));
133  if (o1.ExitCode != 0)
134  throw new InvalidOperationException("Invalid error code from adb install");
135  }
136 
137  // run
138  var parameters = new StringBuilder();
139  parameters.Append("-s "); parameters.Append(device.Serial);
140  parameters.Append(@" shell am start -a android.intent.action.MAIN -n " + packageName + "/nunitlite.tests.MainActivity");
141  AddAndroidParameter(parameters, TestRunner.ParadoxServerIp, serverAddresses);
142  AddAndroidParameter(parameters, TestRunner.ParadoxServerPort, ((IPEndPoint)server.Server.LocalEndPoint).Port.ToString());
143  AddAndroidParameter(parameters, TestRunner.ParadoxBuildNumber, buildNumber.ToString());
144  if (!String.IsNullOrEmpty(branchName))
145  AddAndroidParameter(parameters, TestRunner.ParadoxBranchName, branchName);
146  Console.WriteLine(parameters.ToString());
147 
148  var o2 = ShellHelper.RunProcessAndGetOutput(@"adb", parameters.ToString());
149  Console.WriteLine("adb shell am start: exitcode {0}\nOutput: {1}\nErrors: {2}", o2.ExitCode, string.Join(Environment.NewLine, o2.OutputLines), string.Join(Environment.NewLine, o2.OutputErrors));
150  if (o2.ExitCode != 0)
151  throw new InvalidOperationException("Invalid error code from adb shell am start");
152 
153  // Wait for client to connect
154  var client = server.AcceptTcpClient();
155 
156  Console.WriteLine("Device connected, wait for results...");
157 
158  var clientStream = client.GetStream();
159  var binaryReader = new BinaryReader(clientStream);
160 
161  // Read output
162  var output = binaryReader.ReadString();
163  Console.WriteLine(output);
164 
165  // Read XML result
166  var result = binaryReader.ReadString();
167  Console.WriteLine(result);
168 
169  // Write XML result to disk
170  File.WriteAllText(resultFile, result);
171 
172  return 0;
173  }
174  catch (Exception e)
175  {
176  Console.WriteLine(@"An error was thrown when running the test on Android: {0}", e);
177  return -1;
178  }
179  }
180 
181  /// <summary>
182  /// Add the parameter as an extra in an Android launch command line
183  /// </summary>
184  /// <param name="builder">The string builder.</param>
185  /// <param name="parameterName">The name of the parameter.</param>
186  /// <param name="parameterValue">The value of the parameter.</param>
187  private static void AddAndroidParameter(StringBuilder builder, string parameterName, string parameterValue)
188  {
189  builder.Append(@" -e ");
190  builder.Append(parameterName);
191  builder.Append(@" ");
192  builder.Append(parameterValue);
193  }
194 
195  /// <summary>
196  /// A structure to store information about the connected test devices.
197  /// </summary>
198  public struct ConnectedDevice
199  {
200  public string Serial;
201  public string Name;
203 
204  public override string ToString()
205  {
206  return Name + " " + Serial + " " + PlatformPermutator.GetPlatformName(Platform);
207  }
208  }
209  }
210 
211  class Program
212  {
213  static int Main(string[] args)
214  {
215  var exeName = Path.GetFileName(Assembly.GetExecutingAssembly().Location);
216  var showHelp = false;
217  int exitCode = 0;
218  string resultPath = "TestResults";
219  bool reinstall = true;
220 
221  var p = new OptionSet
222  {
223  "Copyright (C) 2011-2013 Silicon Studio Corporation. All Rights Reserved",
224  "Paradox Test Suite Tool - Version: "
225  +
226  String.Format(
227  "{0}.{1}.{2}",
228  typeof(Program).Assembly.GetName().Version.Major,
229  typeof(Program).Assembly.GetName().Version.Minor,
230  typeof(Program).Assembly.GetName().Version.Build) + string.Empty,
231  string.Format("Usage: {0} [assemblies|apk] -option1 -option2:a", exeName),
232  string.Empty,
233  "=== Options ===",
234  string.Empty,
235  { "h|help", "Show this message and exit", v => showHelp = v != null },
236  { "result-path:", "Result .XML output path", v => resultPath = v },
237  { "no-reinstall-apk", "Do not reinstall APK", v => reinstall = false },
238  };
239 
240  try
241  {
242  var commandArgs = p.Parse(args);
243  if (showHelp)
244  {
245  p.WriteOptionDescriptions(Console.Out);
246  return 0;
247  }
248 
249  // Make sure path exists
250  Directory.CreateDirectory(resultPath);
251  exitCode = BuildAndRunAndroidTests(commandArgs, reinstall, resultPath);
252  }
253  catch (Exception e)
254  {
255  Console.WriteLine("{0}: {1}", exeName, e);
256  if (e is OptionException)
257  p.WriteOptionDescriptions(Console.Out);
258  exitCode = 1;
259  }
260 
261  return exitCode;
262  }
263 
264  private static int BuildAndRunAndroidTests(List<string> commandArgs, bool reinstall, string resultPath)
265  {
266  if (commandArgs.Count == 0)
267  throw new OptionException("One APK should be provided", "apk");
268 
269  // get build number
270  int buildNumber;
271  if (!Int32.TryParse(Environment.GetEnvironmentVariable("PARADOX_BUILD_NUMBER"), out buildNumber))
272  buildNumber = -1;
273 
274  // get branch name
275  var branchName = Environment.GetEnvironmentVariable("PARADOX_BRANCH_NAME");
276 
277  var exitCode = 0;
278 
279  foreach (var packageFile in commandArgs)
280  {
281  if (!packageFile.EndsWith("-Signed.apk"))
282  throw new OptionException("APK should end up with \"-Signed.apk\"", "apk");
283 
284  // Remove -Signed.apk suffix
285  var packageName = Path.GetFileName(packageFile);
286  packageName = packageName.Replace("-Signed.apk", string.Empty);
287 
288  var androidDevices = AndroidDeviceEnumerator.ListAndroidDevices();
289  if (androidDevices.Length == 0)
290  throw new InvalidOperationException("Could not find any Android device connected.");
291 
292  foreach (var device in androidDevices)
293  {
294  var testServerHost = new TestServerHost(buildNumber, branchName);
295  Directory.CreateDirectory(resultPath);
296  var deviceResultFile = Path.Combine(resultPath, "TestResult_" + packageName + "_Android_" + device.Name + "_" + device.Serial + ".xml");
297 
298  var currentExitCode = testServerHost.RunAndroidTest(
300  {
301  Name = device.Name,
302  Serial = device.Serial,
303  Platform = TestPlatform.Android,
304  },
305  reinstall, packageName, packageFile, deviceResultFile);
306  if (currentExitCode != 0)
307  exitCode = currentExitCode;
308  }
309  }
310 
311  return exitCode;
312  }
313  }
314 }
A structure to store information about the connected test devices.
Definition: Program.cs:198
int RunAndroidTest(ConnectedDevice device, bool reinstall, string packageName, string packageFile, string resultFile)
Definition: Program.cs:116