cros_sdk: add --strace for ease of debugging

Trying to run cros_sdk through strace is difficult -- it requires being
root, otherwise the sudo call fails in confusing ways.  Add an --strace
option for devs to easily run things themselves.  This aids in debugging
some of the lower level calls we make in chromite.

BUG=None
TEST=`cros_sdk --strace -- true` works

Change-Id: I879d35d49f45594b9da2cef538ff60ae623a6d47
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/3779418
Reviewed-by: Ram Chandrasekar <rchandrasekar@google.com>
Auto-Submit: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Ram Chandrasekar <rchandrasekar@google.com>
diff --git a/scripts/cros_sdk_unittest.py b/scripts/cros_sdk_unittest.py
index 6812bd5..9366370 100644
--- a/scripts/cros_sdk_unittest.py
+++ b/scripts/cros_sdk_unittest.py
@@ -6,9 +6,12 @@
 
 import logging
 import os
+import re
 import subprocess
+import sys
 import unittest
 
+from chromite.lib import constants
 from chromite.lib import cros_build_lib
 from chromite.lib import cros_sdk_lib
 from chromite.lib import cros_test_lib
@@ -84,6 +87,72 @@
         cros_sdk.FetchRemoteTarballs(self.tempdir, ['gs://x/tar']))
 
 
+class CrosSdkParserCommandLineTest(cros_test_lib.MockTestCase):
+  """Tests involving the CLI."""
+
+  # pylint: disable=protected-access
+
+  # A typical sys.argv[0] that cros_sdk sees.
+  ARGV0 = '/home/chronos/chromiumos/chromite/bin/cros_sdk'
+
+  def setUp(self):
+    self.parser, _ = cros_sdk._CreateParser('1', '2')
+
+  def testSudoCommand(self):
+    """Verify basic sudo command building works."""
+    # Stabilize the env for testing.
+    for v in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
+      os.environ[v] = 'value'
+    os.environ['PATH'] = 'path'
+
+    cmd = cros_sdk._SudoCommand()
+    assert cmd[0] == 'sudo'
+    assert 'CHROMEOS_SUDO_PATH=path' in cmd
+    rlimits = [x for x in cmd if x.startswith('CHROMEOS_SUDO_RLIMITS=')]
+    assert len(rlimits) == 1
+
+    # Spot check some pass thru vars.
+    assert 'GIT_AUTHOR_EMAIL=value' in cmd
+    assert 'https_proxy=value' in cmd
+
+    # Make sure we only pass vars after `sudo`.
+    for i in range(1, len(cmd)):
+      assert '=' in cmd[i]
+      v = cmd[i].split('=', 1)[0]
+      assert re.match(r'^[A-Za-z0-9_]+$', v) is not None
+
+  def testReexecCommand(self):
+    """Verify reexec command line building."""
+    # Stub sudo logic since we tested it above already.
+    self.PatchObject(cros_sdk, '_SudoCommand', return_value=['sudo'])
+    opts = self.parser.parse_args([])
+    new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
+    assert new_cmd == ['sudo', '--', sys.executable, self.ARGV0]
+
+  def testReexecCommandStrace(self):
+    """Verify reexec command line building w/strace."""
+    # Stub sudo logic since we tested it above already.
+    self.PatchObject(cros_sdk, '_SudoCommand', return_value=['sudo'])
+
+    # Strace args passed, but not enabled.
+    opts = self.parser.parse_args(['--strace-arguments=-s4096 -v'])
+    new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
+    assert new_cmd == ['sudo', '--', sys.executable, self.ARGV0]
+
+    # Strace enabled.
+    opts = self.parser.parse_args(['--strace'])
+    new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
+    assert new_cmd == ['sudo', '--', 'strace', '--', sys.executable, self.ARGV0]
+
+    # Strace enabled w/args.
+    opts = self.parser.parse_args(['--strace', '--strace-arguments=-s4096 -v'])
+    new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
+    assert new_cmd == [
+        'sudo', '--', 'strace', '-s4096', '-v', '--', sys.executable,
+        self.ARGV0,
+    ]
+
+
 @unittest.skipIf(cros_build_lib.IsInsideChroot(),
                  'Tests only make sense outside the chroot')
 @unittest.skip(