Revised GPIO module interface + GPIO discovery logic

* The GpioHandler class is no longer a static singleton, rather an
  ordinary object with a dynamic guard against multiple instances. This
  makes testing/mocking a lot easier and simplifies implementation.

* It uses a basic, mockable udev interface; the module comes with
  complete unit testing of the discovery mechanism.

* Corresponding changes to user classes, including UpdateAttempter and
  UpdateCheckScheduler.

Note that the implementation of the test mode signaling protocol is
currently a no-op, always returning false, and hence has no effect on
the update process yet. This mechanism will be implemented in a later
CL.

BUG=chromium-os:25397
TEST=Builds and passes unit tests (including new ones)

Change-Id: I2f6254db6799ff5ef8616314890833f6e3269ff6
Reviewed-on: https://gerrit.chromium.org/gerrit/22869
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Commit-Ready: Gilad Arnold <garnold@chromium.org>
diff --git a/SConstruct b/SConstruct
index 0030bd5..a79a8a8 100644
--- a/SConstruct
+++ b/SConstruct
@@ -304,6 +304,8 @@
                             filesystem_iterator_unittest.cc
                             flimflam_proxy_unittest.cc
                             full_update_generator_unittest.cc
+                            gpio_handler_unittest.cc
+                            gpio_mock_udev_interface.cc
                             graph_utils_unittest.cc
                             http_fetcher_unittest.cc
                             metadata_unittest.cc
diff --git a/gpio_handler.cc b/gpio_handler.cc
index d1825bd..efd5e3d 100644
--- a/gpio_handler.cc
+++ b/gpio_handler.cc
@@ -4,333 +4,250 @@
 
 #include "gpio_handler.h"
 
-#include <fcntl.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <base/eintr_wrapper.h>
+#include <base/memory/scoped_ptr.h>
 #include <base/string_util.h>
-#include <glib.h>
+#include <base/stringprintf.h>
 
-#include "update_engine/utils.h"
+#include "update_engine/file_descriptor.h"
 
+using base::Time;
+using base::TimeDelta;
 using std::string;
 
+using namespace chromeos_update_engine;
+
 namespace chromeos_update_engine {
 
-const char* GpioHandler::dutflaga_dev_name_ = NULL;
-const char* GpioHandler::dutflagb_dev_name_ = NULL;
-
-namespace {
-// Names of udev properties that are linked to the GPIO chip device and identify
-// the two dutflag GPIOs on different boards.
-const char kIdGpioDutflaga[] = "ID_GPIO_DUTFLAGA";
-const char kIdGpioDutflagb[] = "ID_GPIO_DUTFLAGB";
-
-// Scoped closer for udev and udev_enumerate objects.
-// TODO(garnold) chromium-os:26934: it would be nice to generalize the different
-// ScopedFooCloser implementations in update engine using a single template.
-class ScopedUdevCloser {
- public:
-  explicit ScopedUdevCloser(udev** udev_p) : udev_p_(udev_p) {}
-  ~ScopedUdevCloser() {
-    if (udev_p_ && *udev_p_) {
-      udev_unref(*udev_p_);
-      *udev_p_ = NULL;
-    }
-  }
- private:
-  struct udev **udev_p_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedUdevCloser);
+const char* StandardGpioHandler::gpio_dirs_[kGpioDirMax] = {
+  "in",   // kGpioDirIn
+  "out",  // kGpioDirOut
 };
 
-class ScopedUdevEnumerateCloser {
- public:
-  explicit ScopedUdevEnumerateCloser(udev_enumerate **udev_enum_p) :
-    udev_enum_p_(udev_enum_p) {}
-  ~ScopedUdevEnumerateCloser() {
-    if (udev_enum_p_ && *udev_enum_p_) {
-      udev_enumerate_unref(*udev_enum_p_);
-      *udev_enum_p_ = NULL;
-    }
-  }
- private:
-  struct udev_enumerate** udev_enum_p_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedUdevEnumerateCloser);
+const char* StandardGpioHandler::gpio_vals_[kGpioValMax] = {
+  "1",  // kGpioValUp
+  "0",  // kGpioValDown
 };
-}  // namespace {}
 
-bool GpioHandler::IsGpioSignalingTest() {
-  // Peek dut_flaga GPIO state.
-  bool dutflaga_gpio_state;
-  if (!GetDutflagGpioStatus(kDutflagaGpio, &dutflaga_gpio_state)) {
-    LOG(WARNING) << "dutflaga GPIO reading failed, defaulting to non-test mode";
+const StandardGpioHandler::GpioDef
+StandardGpioHandler::gpio_defs_[kGpioIdMax] = {
+  { "dutflaga", "ID_GPIO_DUTFLAGA" },  // kGpioDutflaga
+  { "dutflagb", "ID_GPIO_DUTFLAGB" },  // kGpioDutflagb
+};
+
+unsigned StandardGpioHandler::num_instances_ = 0;
+
+
+StandardGpioHandler::StandardGpioHandler(UdevInterface* udev_iface,
+                                         bool is_defer_discovery)
+    : udev_iface_(udev_iface),
+      is_discovery_attempted_(false) {
+  CHECK(udev_iface);
+
+  // Ensure there's only one instance of this class.
+  CHECK_EQ(num_instances_, static_cast<unsigned>(0));
+  num_instances_++;
+
+  // If GPIO discovery not deferred, do it.
+  if (!(is_defer_discovery || DiscoverGpios())) {
+    LOG(WARNING) << "GPIO discovery failed";
+  }
+}
+
+StandardGpioHandler::~StandardGpioHandler() {
+  num_instances_--;
+}
+
+// TODO(garnold) currently, this function always returns false and avoids the
+// GPIO signaling protocol altogether; to be extended later.
+bool StandardGpioHandler::IsTestModeSignaled() {
+  // Attempt GPIO discovery.
+  if (!DiscoverGpios()) {
+    LOG(WARNING) << "GPIO discovery failed";
+  }
+
+  return false;
+}
+
+bool StandardGpioHandler::GpioChipUdevEnumHelper::SetupEnumFilters(
+    udev_enumerate* udev_enum) {
+  CHECK(udev_enum);
+
+  return !(gpio_handler_->udev_iface_->EnumerateAddMatchSubsystem(
+               udev_enum, "gpio") ||
+           gpio_handler_->udev_iface_->EnumerateAddMatchSysname(
+               udev_enum, "gpiochip*"));
+}
+
+bool StandardGpioHandler::GpioChipUdevEnumHelper::ProcessDev(udev_device* dev) {
+  CHECK(dev);
+
+  // Ensure we did not encounter more than one chip.
+  if (num_gpio_chips_++) {
+    LOG(ERROR) << "enumerated multiple GPIO chips";
     return false;
   }
 
-  LOG(INFO) << "dutflaga GPIO reading: "
-            << (dutflaga_gpio_state ? "on (non-test mode)" : "off (test mode)");
-  return !dutflaga_gpio_state;
+  // Obtain GPIO descriptors.
+  for (int id = 0; id < kGpioIdMax; id++) {
+    const GpioDef* gpio_def = &gpio_defs_[id];
+    const char* descriptor =
+        gpio_handler_->udev_iface_->DeviceGetPropertyValue(
+            dev, gpio_def->udev_property);
+    if (!descriptor) {
+      LOG(ERROR) << "could not obtain " << gpio_def->name
+                 << " descriptor using property " << gpio_def->udev_property;
+      return false;
+    }
+    gpio_handler_->gpios_[id].descriptor = descriptor;
+  }
+
+  return true;
 }
 
-bool GpioHandler::GetDutflagGpioDevName(struct udev* udev,
-                                        const string& gpio_dutflag_str,
-                                        const char** dutflag_dev_name_p) {
-  CHECK(udev && dutflag_dev_name_p);
+bool StandardGpioHandler::GpioChipUdevEnumHelper::Finalize() {
+  if (num_gpio_chips_ != 1) {
+    LOG(ERROR) << "could not enumerate a GPIO chip";
+    return false;
+  }
+  return true;
+}
 
-  struct udev_enumerate* udev_enum = NULL;
-  int num_gpio_dutflags = 0;
-  const string gpio_dutflag_pattern = "*" + gpio_dutflag_str;
-  int ret;
+bool StandardGpioHandler::GpioUdevEnumHelper::SetupEnumFilters(
+    udev_enumerate* udev_enum) {
+  CHECK(udev_enum);
+  const string gpio_pattern =
+      string("*").append(gpio_handler_->gpios_[id_].descriptor);
+  return !(
+      gpio_handler_->udev_iface_->EnumerateAddMatchSubsystem(
+          udev_enum, "gpio") ||
+      gpio_handler_->udev_iface_->EnumerateAddMatchSysname(
+          udev_enum, gpio_pattern.c_str()));
+}
 
-  // Initialize udev enumerate context and closer.
-  if (!(udev_enum = udev_enumerate_new(udev))) {
+bool StandardGpioHandler::GpioUdevEnumHelper::ProcessDev(udev_device* dev) {
+  CHECK(dev);
+
+  // Ensure we did not encounter more than one GPIO device.
+  if (num_gpios_++) {
+    LOG(ERROR) << "enumerated multiple GPIO devices for a given descriptor";
+    return false;
+  }
+
+  // Obtain GPIO device sysfs path.
+  const char* dev_path = gpio_handler_->udev_iface_->DeviceGetSyspath(dev);
+  if (!dev_path) {
+    LOG(ERROR) << "failed to obtain device syspath for GPIO "
+               << gpio_defs_[id_].name;
+    return false;
+  }
+  gpio_handler_->gpios_[id_].dev_path = dev_path;
+
+  LOG(INFO) << "obtained device syspath: " << gpio_defs_[id_].name << " -> "
+            << gpio_handler_->gpios_[id_].dev_path;
+  return true;
+}
+
+bool StandardGpioHandler::GpioUdevEnumHelper::Finalize() {
+  if (num_gpios_ != 1) {
+    LOG(ERROR) << "could not enumerate GPIO device " << gpio_defs_[id_].name;
+    return false;
+  }
+  return true;
+}
+
+
+bool StandardGpioHandler::InitUdevEnum(struct udev* udev,
+                                       UdevEnumHelper* enum_helper) {
+  // Obtain a udev enumerate object.
+  struct udev_enumerate* udev_enum;
+  if (!(udev_enum = udev_iface_->EnumerateNew(udev))) {
     LOG(ERROR) << "failed to obtain udev enumerate context";
     return false;
   }
-  ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
 
-  // Populate filters for find an initialized GPIO chip.
-  if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
-      (ret = udev_enumerate_add_match_sysname(udev_enum,
-                                              gpio_dutflag_pattern.c_str()))) {
-    LOG(ERROR) << "failed to initialize udev enumerate context (" << ret << ")";
+  // Assign enumerate object to closer.
+  scoped_ptr<UdevInterface::UdevEnumerateCloser>
+      udev_enum_closer(udev_iface_->NewUdevEnumerateCloser(&udev_enum));
+
+  // Setup enumeration filters.
+  if (!enum_helper->SetupEnumFilters(udev_enum)) {
+    LOG(ERROR) << "failed to setup udev enumerate filters";
     return false;
   }
 
-  // Obtain list of matching devices.
-  if ((ret = udev_enumerate_scan_devices(udev_enum))) {
-    LOG(ERROR) << "udev enumerate context scan failed (error code "
-               << ret << ")";
+  // Scan for matching devices.
+  if (udev_iface_->EnumerateScanDevices(udev_enum)) {
+    LOG(ERROR) << "udev enumerate scan failed";
     return false;
   }
 
-  // Iterate over matching devices, obtain GPIO dut_flaga identifier.
+  // Iterate over matching devices.
   struct udev_list_entry* list_entry;
-  udev_list_entry_foreach(list_entry,
-                          udev_enumerate_get_list_entry(udev_enum)) {
-    // Make sure we're not enumerating more than one device.
-    num_gpio_dutflags++;
-    if (num_gpio_dutflags > 1) {
-      LOG(WARNING) <<
-        "enumerated multiple dutflag GPIOs, ignoring this one";
-      continue;
-    }
-
+  for (list_entry = udev_iface_->EnumerateGetListEntry(udev_enum);
+       list_entry; list_entry = udev_iface_->ListEntryGetNext(list_entry)) {
     // Obtain device name.
-    const char* dev_name = udev_list_entry_get_name(list_entry);
-    if (!dev_name) {
-      LOG(WARNING) << "enumerated device has a null name string, skipping";
-      continue;
+    const char* dev_path = udev_iface_->ListEntryGetName(list_entry);
+    if (!dev_path) {
+      LOG(ERROR) << "enumerated device has a null name string";
+      return false;
     }
 
     // Obtain device object.
-    struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
+    struct udev_device* dev = udev_iface_->DeviceNewFromSyspath(udev, dev_path);
     if (!dev) {
-      LOG(WARNING) <<
-        "obtained a null device object for enumerated device, skipping";
-      continue;
-    }
-
-    // Obtain device syspath.
-    const char* dev_syspath = udev_device_get_syspath(dev);
-    if (dev_syspath) {
-      LOG(INFO) << "obtained device syspath: " << dev_syspath;
-      *dutflag_dev_name_p = strdup(dev_syspath);
-    } else {
-      LOG(WARNING) << "could not obtain device syspath";
-    }
-
-    udev_device_unref(dev);
-  }
-
-  return true;
-}
-
-bool GpioHandler::GetDutflagGpioDevNames(string* dutflaga_dev_name_p,
-                                         string* dutflagb_dev_name_p) {
-  if (!(dutflaga_dev_name_p || dutflagb_dev_name_p))
-    return true;  // No output pointers, nothing to do.
-
-  string gpio_dutflaga_str, gpio_dutflagb_str;
-
-  if (!(dutflaga_dev_name_ && dutflagb_dev_name_)) {
-    struct udev* udev = NULL;
-    struct udev_enumerate* udev_enum = NULL;
-    int num_gpio_chips = 0;
-    const char* id_gpio_dutflaga = NULL;
-    const char* id_gpio_dutflagb = NULL;
-    int ret;
-
-    LOG(INFO) << "begin discovery of dut_flaga/b devices";
-
-    // Obtain libudev instance and closer.
-    if (!(udev = udev_new())) {
-      LOG(ERROR) << "failed to obtain libudev instance";
+      LOG(ERROR) << "obtained a null device object for enumerated device";
       return false;
     }
-    ScopedUdevCloser udev_closer(&udev);
+    scoped_ptr<UdevInterface::UdevDeviceCloser>
+        dev_closer(udev_iface_->NewUdevDeviceCloser(&dev));
 
-    // Initialize a udev enumerate object and closer with a bounded lifespan.
-    {
-      if (!(udev_enum = udev_enumerate_new(udev))) {
-        LOG(ERROR) << "failed to obtain udev enumerate context";
-        return false;
-      }
-      ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
-
-      // Populate filters for find an initialized GPIO chip.
-      if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
-          (ret = udev_enumerate_add_match_sysname(udev_enum, "gpiochip*")) ||
-          (ret = udev_enumerate_add_match_property(udev_enum,
-                                                   kIdGpioDutflaga, "*")) ||
-          (ret = udev_enumerate_add_match_property(udev_enum,
-                                                   kIdGpioDutflagb, "*"))) {
-        LOG(ERROR) << "failed to initialize udev enumerate context ("
-                   << ret << ")";
-        return false;
-      }
-
-      // Obtain list of matching devices.
-      if ((ret = udev_enumerate_scan_devices(udev_enum))) {
-        LOG(ERROR) << "udev enumerate context scan failed (" << ret << ")";
-        return false;
-      }
-
-      // Iterate over matching devices, obtain GPIO dut_flaga identifier.
-      struct udev_list_entry* list_entry;
-      udev_list_entry_foreach(list_entry,
-                              udev_enumerate_get_list_entry(udev_enum)) {
-        // Make sure we're not enumerating more than one device.
-        num_gpio_chips++;
-        if (num_gpio_chips > 1) {
-          LOG(WARNING) << "enumerated multiple GPIO chips, ignoring this one";
-          continue;
-        }
-
-        // Obtain device name.
-        const char* dev_name = udev_list_entry_get_name(list_entry);
-        if (!dev_name) {
-          LOG(WARNING) << "enumerated device has a null name string, skipping";
-          continue;
-        }
-
-        // Obtain device object.
-        struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
-        if (!dev) {
-          LOG(WARNING) <<
-            "obtained a null device object for enumerated device, skipping";
-          continue;
-        }
-
-        // Obtain dut_flaga/b identifiers.
-        id_gpio_dutflaga =
-            udev_device_get_property_value(dev, kIdGpioDutflaga);
-        id_gpio_dutflagb =
-            udev_device_get_property_value(dev, kIdGpioDutflagb);
-        if (id_gpio_dutflaga && id_gpio_dutflagb) {
-          LOG(INFO) << "found dut_flaga/b identifiers: a=" << id_gpio_dutflaga
-                    << " b=" << id_gpio_dutflagb;
-
-          gpio_dutflaga_str = id_gpio_dutflaga;
-          gpio_dutflagb_str = id_gpio_dutflagb;
-        } else {
-          LOG(ERROR) << "GPIO chip missing dut_flaga/b properties";
-        }
-
-        udev_device_unref(dev);
-      }
-    }
-
-    // Obtain dut_flaga, reusing the same udev instance.
-    if (!dutflaga_dev_name_ && !gpio_dutflaga_str.empty()) {
-      LOG(INFO) << "discovering device for GPIO dut_flaga ";
-      if (!GetDutflagGpioDevName(udev, gpio_dutflaga_str,
-                                 &dutflaga_dev_name_)) {
-        LOG(ERROR) << "discovery of dut_flaga GPIO device failed";
-        return false;
-      }
-    }
-
-    // Now obtain dut_flagb.
-    if (!dutflagb_dev_name_ && !gpio_dutflagb_str.empty()) {
-      LOG(INFO) << "discovering device for GPIO dut_flagb";
-      if (!GetDutflagGpioDevName(udev, gpio_dutflagb_str,
-                                 &dutflagb_dev_name_)) {
-        LOG(ERROR) << "discovery of dut_flagb GPIO device failed";
-        return false;
-      }
-    }
-
-    LOG(INFO) << "end discovery of dut_flaga/b devices";
+    if (!enum_helper->ProcessDev(dev))
+      return false;
   }
 
-  // Write cached GPIO dutflag(s) to output strings.
-  if (dutflaga_dev_name_p && dutflaga_dev_name_)
-    *dutflaga_dev_name_p = dutflaga_dev_name_;
-  if (dutflagb_dev_name_p && dutflagb_dev_name_)
-    *dutflagb_dev_name_p = dutflagb_dev_name_;
+  // Make sure postconditions were met.
+  return enum_helper->Finalize();
+}
+
+bool StandardGpioHandler::DiscoverGpios() {
+  if (is_discovery_attempted_)
+    return true;
+
+  is_discovery_attempted_ = true;
+
+  // Obtain libudev instance and attach to a dedicated closer.
+  struct udev* udev;
+  if (!(udev = udev_iface_->New())) {
+    LOG(ERROR) << "failed to obtain libudev instance";
+    return false;
+  }
+  scoped_ptr<UdevInterface::UdevCloser>
+      udev_closer(udev_iface_->NewUdevCloser(&udev));
+
+  // Enumerate GPIO chips, scanning for GPIO descriptors.
+  GpioChipUdevEnumHelper chip_enum_helper(this);
+  if (!InitUdevEnum(udev, &chip_enum_helper)) {
+    LOG(ERROR) << "enumeration error, aborting GPIO discovery";
+    return false;
+  }
+
+  // Obtain device names for all discovered GPIOs, reusing the udev instance.
+  for (int id = 0; id < kGpioIdMax; id++) {
+    GpioUdevEnumHelper gpio_enum_helper(this, static_cast<GpioId>(id));
+    if (!InitUdevEnum(udev, &gpio_enum_helper)) {
+      LOG(ERROR) << "enumeration error, aborting GPIO discovery";
+      return false;
+    }
+  }
 
   return true;
 }
 
-bool GpioHandler::GetDutflagGpioStatus(GpioHandler::DutflagGpioId id,
-                                       bool* status_p) {
-  CHECK(status_p);
+bool StandardGpioHandler::GetGpioDevName(StandardGpioHandler::GpioId id,
+                                         string* dev_path_p) {
+  CHECK(id >= 0 && id < kGpioIdMax && dev_path_p);
 
-  // Obtain GPIO device file name.
-  string dutflag_dev_name;
-  switch (id) {
-    case kDutflagaGpio:
-      GetDutflagGpioDevNames(&dutflag_dev_name, NULL);
-      break;
-    case kDutflagbGpio:
-      GetDutflagGpioDevNames(NULL, &dutflag_dev_name);
-      break;
-    default:
-      LOG(FATAL) << "invalid dutflag GPIO id: " << id;
-  }
-
-  if (dutflag_dev_name.empty()) {
-    LOG(WARNING) << "could not find dutflag GPIO device";
-    return false;
-  }
-
-  // Open device for reading.
-  string dutflaga_value_dev_name = dutflag_dev_name + "/value";
-  int dutflaga_fd = HANDLE_EINTR(open(dutflaga_value_dev_name.c_str(), 0));
-  if (dutflaga_fd < 0) {
-    PLOG(ERROR) << "opening dutflaga GPIO device file failed";
-    return false;
-  }
-  ScopedEintrSafeFdCloser dutflaga_fd_closer(&dutflaga_fd);
-
-  // Read the dut_flaga GPIO signal. We attempt to read more than---but expect
-  // to receive exactly---two characters: a '0' or '1', and a newline. This is
-  // to ensure that the GPIO device returns a legible result.
-  char buf[3];
-  int ret = HANDLE_EINTR(read(dutflaga_fd, buf, 3));
-  if (ret != 2) {
-    if (ret < 0)
-      PLOG(ERROR) << "reading dutflaga GPIO status failed";
-    else
-      LOG(ERROR) << "read more than one byte (" << ret << ")";
-    return false;
-  }
-
-  // Identify and write GPIO status.
-  char c = buf[0];
-  if ((c == '0' || c == '1') && buf[1] == '\n') {
-    *status_p = (c == '1');
-  } else {
-    buf[2] = '\0';
-    LOG(ERROR) << "read unexpected value from dutflaga GPIO: " << buf;
-    return false;
-  }
-
+  *dev_path_p = gpios_[id].dev_path;
   return true;
 }
 
diff --git a/gpio_handler.h b/gpio_handler.h
index c3d5a38..b269266 100644
--- a/gpio_handler.h
+++ b/gpio_handler.h
@@ -9,61 +9,192 @@
 
 #include <string>
 
-#include <base/basictypes.h>
+#include <base/time.h>
+
+#include "update_engine/file_descriptor.h"
+#include "update_engine/udev_interface.h"
 
 namespace chromeos_update_engine {
 
-// Handles GPIO signals, which are used for indicating a lab test environment.
-// This class detects, reads and decides whether a test scenario is in effect,
-// which can be used by client code to trigger test-specific behavior.  This
-// class has only static members/methods and cannot be instantiated.
+// An abstract GPIO handler interface. Serves as basic for both concrete and
+// mock implementations.
 class GpioHandler {
  public:
-  // Returns true iff GPIOs have been signaled to indicate an automated test
-  // case. This triggers a discovery and reading of the dutflaga/b GPIOs.
-  static bool IsGpioSignalingTest();
+  GpioHandler() {}
+  virtual ~GpioHandler() {};  // ensure virtual destruction
+
+  // Returns true iff GPIOs have been used to signal an automated test case.
+  // This call may trigger a (deferred) GPIO discovery step prior to engaging in
+  // the signaling protocol; if discovery did not reveal GPIO devices, or the
+  // protocol has terminated prematurely, it will conservatively default to
+  // false.
+  virtual bool IsTestModeSignaled() = 0;
 
  private:
-  // This class cannot be instantiated.
-  GpioHandler() {}
+  DISALLOW_COPY_AND_ASSIGN(GpioHandler);
+};
 
-  // Enumerator for dutflag GPIOs.
-  enum DutflagGpioId {
-    kDutflagaGpio,
-    kDutflagbGpio,
+
+// Concrete implementation of GPIO signal handling. Currently, it only utilizes
+// the two Chromebook-specific GPIOs (aka 'dut_flaga' and 'dut_flagb') in
+// deciding whether a lab test mode has been signaled.  Internal logic includes
+// detection, setup and reading from / writing to GPIOs. Detection is done via
+// libudev calls.  This class should be instantiated at most once to avoid race
+// conditions in communicating over GPIO signals; instantiating a second
+// object will actually cause a runtime error.
+class StandardGpioHandler : public GpioHandler {
+ public:
+  // This constructor accepts a udev interface |udev_iface|. The value of
+  // |is_defer_discovery| determines whether GPIO discovery should be attempted
+  // right away (false) or done lazily, when necessitated by other calls (true).
+  StandardGpioHandler(UdevInterface* udev_iface, bool is_defer_discovery);
+
+  // Free all resources, allow to reinstantiate.
+  virtual ~StandardGpioHandler();
+
+  // Returns true iff GPIOs have been used to signal an automated test case. The
+  // check is performed at most once and the result is cached and returned on
+  // subsequent calls, unless |is_force| is true. This call may trigger a
+  // delayed GPIO discovery prior to engaging in the signaling protocol; if the
+  // delay period has not elapsed, it will conservatively default to false.
+  virtual bool IsTestModeSignaled();
+
+ private:
+  // GPIO identifiers, currently includes only the two dutflags.
+  enum GpioId {
+    kGpioIdDutflaga = 0,
+    kGpioIdDutflagb,
+    kGpioIdMax  // marker, do not remove!
   };
 
-  // Gets the fully qualified sysfs name of a dutflag device.  |udev| is a live
-  // libudev instance; |gpio_dutflag_str| is the identifier for the requested
-  // dutflag GPIO. The output is stored in the string pointed to by
-  // |dutflag_dev_name_p|.  Returns true upon success, false otherwise.
-  static bool GetDutflagGpioDevName(struct udev* udev,
-                                    const std::string& gpio_dutflag_str,
-                                    const char** dutflag_dev_name_p);
+  // GPIO direction specifier.
+  enum GpioDir {
+    kGpioDirIn = 0,
+    kGpioDirOut,
+    kGpioDirMax  // marker, do not remove!
+  };
 
-  // Gets the dut_flaga/b GPIO device names and copies them into the two string
-  // arguments, respectively. A string pointer may be null, in which case
-  // discovery will not be attempted for the corresponding device. The function
-  // caches these strings, which are assumed to be hardware constants. Returns
+  // GPIO value.
+  enum GpioVal {
+    kGpioValUp = 0,
+    kGpioValDown,
+    kGpioValMax  // marker, do not remove!
+  };
+
+  // GPIO definition data.
+  struct GpioDef {
+    const char* name;           // referential name of this GPIO
+    const char* udev_property;  // udev property containing the device id
+  };
+
+  // GPIO runtime control structure.
+  struct Gpio {
+    std::string descriptor;  // unique GPIO descriptor
+    std::string dev_path;    // sysfs device name
+  };
+
+  // Various constants.
+  static const int kServoInputResponseTimeoutInSecs = 3;
+  static const int kServoInputNumChecksPerSec = 5;
+  static const int kServoOutputResponseWaitInSecs = 2;
+
+  // GPIO value/direction conversion tables.
+  static const char* gpio_dirs_[kGpioDirMax];
+  static const char* gpio_vals_[kGpioValMax];
+
+  // GPIO definitions.
+  static const GpioDef gpio_defs_[kGpioIdMax];
+
+  // Udev device enumeration helper classes. First here is an interface
+  // definition, which provides callbacks for enumeration setup and processing.
+  class UdevEnumHelper {
+   public:
+    UdevEnumHelper(StandardGpioHandler* gpio_handler)
+        : gpio_handler_(gpio_handler) {}
+
+    // Setup the enumeration filters.
+    virtual bool SetupEnumFilters(udev_enumerate* udev_enum) = 0;
+
+    // Processes an enumerated device. Returns true upon success, false
+    // otherwise.
+    virtual bool ProcessDev(udev_device* dev) = 0;
+
+    // Finalize the enumeration.
+    virtual bool Finalize() = 0;
+
+   protected:
+    StandardGpioHandler* gpio_handler_;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(UdevEnumHelper);
+  };
+
+  // Specialized udev enumerate helper for extracting GPIO descriptors from the
+  // GPIO chip device.
+  class GpioChipUdevEnumHelper : public UdevEnumHelper {
+   public:
+    GpioChipUdevEnumHelper(StandardGpioHandler* gpio_handler)
+        : UdevEnumHelper(gpio_handler), num_gpio_chips_(0) {}
+    virtual bool SetupEnumFilters(udev_enumerate* udev_enum);
+    virtual bool ProcessDev(udev_device* dev);
+    virtual bool Finalize();
+
+   private:
+    // Records the number of times a GPIO chip has been enumerated (should not
+    // exceed 1).
+    int num_gpio_chips_;
+
+    DISALLOW_COPY_AND_ASSIGN(GpioChipUdevEnumHelper);
+  };
+
+  // Specialized udev enumerate helper for extracting a sysfs device path from a
+  // GPIO device.
+  class GpioUdevEnumHelper : public UdevEnumHelper {
+   public:
+    GpioUdevEnumHelper(StandardGpioHandler* gpio_handler, GpioId id)
+        : UdevEnumHelper(gpio_handler), num_gpios_(0), id_(id) {}
+    virtual bool SetupEnumFilters(udev_enumerate* udev_enum);
+    virtual bool ProcessDev(udev_device* dev);
+    virtual bool Finalize();
+
+   private:
+    // Records the number of times a GPIO has been enumerated with a given
+    // descriptor (should not exceed 1).
+    int num_gpios_;
+
+    // The enumerated GPIO identifier.
+    GpioId id_;
+
+    DISALLOW_COPY_AND_ASSIGN(GpioUdevEnumHelper);
+  };
+
+  // Attempt GPIO discovery, at most once. Returns true if discovery process was
+  // successfully completed or already attempted, false otherwise.
+  bool DiscoverGpios();
+
+  // An initialization helper performing udev enumeration. |enum_helper|
+  // implements an enumeration initialization and processing methods. Returns
   // true upon success, false otherwise.
-  static bool GetDutflagGpioDevNames(std::string* dutflaga_dev_name_p,
-                                     std::string* dutflagb_dev_name_p);
+  bool InitUdevEnum(struct udev* udev, UdevEnumHelper* enum_helper);
 
-  // Writes the dut_flaga GPIO status into its argument, where true/false stand
-  // for "on"/"off", respectively. Returns true upon success, false otherwise
-  // (in which case no value is written to |status|).
-  static bool GetDutflagaGpio(bool* status);
+  // Assigns a copy of the device name of GPIO |id| to |dev_path_p|. Assumes
+  // initialization. Returns true upon success, false otherwise.
+  bool GetGpioDevName(GpioId id, std::string* dev_path_p);
 
-  // Reads the value of a dut_flag GPIO |id| and stores it in |status_p|.
-  // Returns true upon success, false otherwise (which also means that the GPIO
-  // value was not stored and should not be used).
-  static bool GetDutflagGpioStatus(DutflagGpioId id, bool* status_p);
+  // Dynamic counter for the number of instances this class has. Used to enforce
+  // that no more than one instance created. Thread-unsafe.
+  static unsigned num_instances_;
 
-  // Dutflaga/b GPIO device names.
-  static const char* dutflaga_dev_name_;
-  static const char* dutflagb_dev_name_;
+  // GPIO control structures.
+  Gpio gpios_[kGpioIdMax];
 
-  DISALLOW_COPY_AND_ASSIGN(GpioHandler);
+  // Udev interface.
+  UdevInterface* const udev_iface_;
+
+  // Indicates whether GPIO discovery was performed.
+  bool is_discovery_attempted_;
+
+  DISALLOW_COPY_AND_ASSIGN(StandardGpioHandler);
 };
 
 }  // namespace chromeos_update_engine
diff --git a/gpio_handler_unittest.cc b/gpio_handler_unittest.cc
new file mode 100644
index 0000000..a133ac7
--- /dev/null
+++ b/gpio_handler_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium OS 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 <gtest/gtest.h>
+
+#include "update_engine/gpio_handler.h"
+#include "update_engine/gpio_mock_udev_interface.h"
+
+// Some common strings used by the different cooperating mocks for this module.
+// We use preprocessor constants to allow concatenation at compile-time.
+#define MOCK_GPIO_CHIP_ID     "100"
+#define MOCK_DUTFLAGA_GPIO_ID "101"
+#define MOCK_DUTFLAGB_GPIO_ID "102"
+#define MOCK_SYSFS_PREFIX     "/mock/sys/class/gpio"
+
+namespace chromeos_update_engine {
+
+class StandardGpioHandlerTest : public ::testing::Test {};
+
+TEST(StandardGpioHandlerTest, NormalInitTest) {
+  // Ensure that initialization of the GPIO module works as expected, and that
+  // all udev resources are deallocated afterwards.  The mock file descriptor is
+  // not to be used.
+  StandardGpioMockUdevInterface mock_udev;
+  StandardGpioHandler gpio_hander(&mock_udev, false);
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_udev.ExpectDiscoverySuccess();
+}
+
+TEST(StandardGpioHandlerTest, MultiGpioChipInitTest) {
+  // Attempt GPIO discovery with a udev mock that returns two GPIO chip devices.
+  // It should fail, of course.  The mock file descriptor is not to be used.
+  MultiChipGpioMockUdevInterface mock_udev;
+  StandardGpioHandler gpio_handler(&mock_udev, false);
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_udev.ExpectDiscoveryFail();
+}
+
+TEST(StandardGpioHandlerTest, NormalModeGpioSignalingTest) {
+  // Initialize the GPIO module, run the signaling procedure, ensure that it
+  // concluded that this is a normal mode run.
+  StandardGpioMockUdevInterface mock_udev;
+  StandardGpioHandler gpio_handler(&mock_udev, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+}
+
+TEST(StandardGpioHandlerTest, DeferredInitNormalModeGpioSignalingTest) {
+  // Initialize the GPIO module with deferred discovery, run the signaling
+  // procedure, ensure that it concluded that this is a normal mode run.
+  StandardGpioMockUdevInterface mock_udev;
+  StandardGpioHandler gpio_handler(&mock_udev, true);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/gpio_handler_unittest.h b/gpio_handler_unittest.h
new file mode 100644
index 0000000..5780eaa
--- /dev/null
+++ b/gpio_handler_unittest.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_UNITTEST_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_UNITTEST_H__
+
+// This file contains various definitions that are shared by different mock
+// implementations that emulate GPIO behavior in the system.
+
+// Some common strings used by the different cooperating mocks for this module.
+// We use preprocessor constants to allow concatenation at compile-time.
+#define MOCK_GPIO_CHIP_ID     "100"
+#define MOCK_DUTFLAGA_GPIO_ID "101"
+#define MOCK_DUTFLAGB_GPIO_ID "102"
+#define MOCK_SYSFS_PREFIX     "/mock/sys/class/gpio"
+
+namespace chromeos_update_engine {
+
+// Mock GPIO identifiers, used by all mocks involved in unit testing the GPIO
+// module. These represent the GPIOs which the unit tests can cover. They should
+// generally match the GPIOs specified inside GpioHandler.
+enum MockGpioId {
+  kMockGpioIdDutflaga = 0,
+  kMockGpioIdDutflagb,
+  kMockGpioIdMax  // marker, do not remove!
+};
+
+}  // chromeos_update_engine
+
+#endif /* CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_UNITTEST_H__ */
diff --git a/gpio_mock_udev_interface.cc b/gpio_mock_udev_interface.cc
new file mode 100644
index 0000000..22c7b7f
--- /dev/null
+++ b/gpio_mock_udev_interface.cc
@@ -0,0 +1,371 @@
+// Copyright (c) 2012 The Chromium OS 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 "update_engine/gpio_mock_udev_interface.h"
+
+#include <string>
+
+#include <base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "gpio_handler_unittest.h"
+
+namespace chromeos_update_engine {
+
+const char* StandardGpioMockUdevInterface::kUdevGpioSubsystem = "gpio";
+const char* StandardGpioMockUdevInterface::kUdevGpioChipSysname = "gpiochip*";
+
+const StandardGpioMockUdevInterface::GpioDescriptor
+    StandardGpioMockUdevInterface::gpio_descriptors_[kMockGpioIdMax] = {
+  { "ID_GPIO_DUTFLAGA", MOCK_DUTFLAGA_GPIO_ID },
+  { "ID_GPIO_DUTFLAGB", MOCK_DUTFLAGB_GPIO_ID },
+};
+
+const char* StandardGpioMockUdevInterface::enum_gpio_chip_dev_list_[] = {
+  "gpiochip" MOCK_GPIO_CHIP_ID,
+  NULL
+};
+const char* StandardGpioMockUdevInterface::enum_dutflaga_gpio_dev_list_[] = {
+  "gpio" MOCK_DUTFLAGA_GPIO_ID,
+  NULL
+};
+const char* StandardGpioMockUdevInterface::enum_dutflagb_gpio_dev_list_[] = {
+  "gpio" MOCK_DUTFLAGB_GPIO_ID,
+  NULL
+};
+
+StandardGpioMockUdevInterface::StandardGpioMockUdevInterface()
+    : udev_id_(0), udev_enum_id_(0), num_discovered_(0) {
+  std::fill_n(used_udev_ids_, MAX_UDEV_OBJECTS, false);
+  std::fill_n(used_udev_enum_ids_, MAX_UDEV_ENUM_OBJECTS, false);
+
+  gpio_chip_dev_ = (MockDevice) {
+    MOCK_SYSFS_PREFIX "/gpiochip" MOCK_GPIO_CHIP_ID,
+    gpio_descriptors_, kMockGpioIdMax, false, false
+  };
+  dutflaga_gpio_dev_ = (MockDevice) {
+    MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGA_GPIO_ID, NULL, 0, true, false
+  };
+  dutflagb_gpio_dev_ = (MockDevice) {
+    MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGB_GPIO_ID, NULL, 0, true, false
+  };
+}
+
+// Device lists are simply null-terminated arrays of strings.
+const char* StandardGpioMockUdevInterface::ListEntryGetName(
+    struct udev_list_entry* list_entry) {
+  const char** mock_list_entry = reinterpret_cast<const char**>(list_entry);
+  EXPECT_TRUE(mock_list_entry && *mock_list_entry);
+  if (!mock_list_entry)
+    return NULL;
+  // The mock list entry is the name string itself...
+  return *mock_list_entry;
+}
+
+struct udev_list_entry* StandardGpioMockUdevInterface::ListEntryGetNext(
+    struct udev_list_entry* list_entry) {
+  char** mock_list_entry = reinterpret_cast<char**>(list_entry);
+  EXPECT_TRUE(mock_list_entry && *mock_list_entry);
+  if (!mock_list_entry)
+    return NULL;
+  // Advance to the next element in the array.
+  mock_list_entry++;
+  return (*mock_list_entry ?
+          reinterpret_cast<struct udev_list_entry*>(mock_list_entry) : NULL);
+}
+
+struct udev_device* StandardGpioMockUdevInterface::DeviceNewFromSyspath(
+    struct udev* udev, const char* syspath) {
+  const size_t udev_id = UdevToId(udev);
+  if (udev_id >= MAX_UDEV_OBJECTS)
+    return NULL;
+  MockDevice* mock_dev;
+
+  // Generate the desired mock device based on the provided syspath.
+  if (!strcmp(syspath, enum_gpio_chip_dev_list_[0])) {
+    mock_dev = const_cast<MockDevice*>(&gpio_chip_dev_);
+  } else if (!strcmp(syspath, enum_dutflaga_gpio_dev_list_[0])) {
+    mock_dev = const_cast<MockDevice*>(&dutflaga_gpio_dev_);
+  } else if (!strcmp(syspath, enum_dutflagb_gpio_dev_list_[0])) {
+    mock_dev = const_cast<MockDevice*>(&dutflagb_gpio_dev_);
+  } else {
+    ADD_FAILURE();
+    return NULL;
+  }
+
+  EXPECT_FALSE(mock_dev->is_used);
+  if (mock_dev->is_used)
+    return NULL;
+  mock_dev->is_used = true;
+
+  return reinterpret_cast<struct udev_device*>(mock_dev);
+}
+
+const char* StandardGpioMockUdevInterface::DeviceGetPropertyValue(
+    struct udev_device* udev_device, const char* key) {
+  const MockDevice* mock_dev = UdevDeviceToMock(udev_device);
+  EXPECT_TRUE(mock_dev->properties);
+  if (!mock_dev->properties)
+    return NULL;
+  for (size_t i = 0; i < mock_dev->num_properties; i++)
+    if (!strcmp(key, mock_dev->properties[i].property))
+      return mock_dev->properties[i].value;
+  return NULL;
+}
+
+const char* StandardGpioMockUdevInterface::DeviceGetSyspath(
+    struct udev_device* udev_device) {
+  const MockDevice* mock_dev = UdevDeviceToMock(udev_device);
+  if (mock_dev->is_gpio)
+    num_discovered_++;
+  return mock_dev->syspath;
+}
+
+void StandardGpioMockUdevInterface::DeviceUnref(
+    struct udev_device* udev_device) {
+  MockDevice* mock_dev = UdevDeviceToMock(udev_device);
+  mock_dev->is_used = false;
+}
+
+struct udev_enumerate* StandardGpioMockUdevInterface::EnumerateNew(
+    struct udev* udev) {
+  const size_t udev_id = UdevToId(udev);
+  if (udev_id >= MAX_UDEV_OBJECTS)
+    return NULL;
+  // A new enumeration "pointer" is cast for an integer identifier.
+  EXPECT_LT(udev_enum_id_, MAX_UDEV_ENUM_OBJECTS);
+  if (udev_enum_id_ >= MAX_UDEV_ENUM_OBJECTS)
+    return NULL;
+  used_udev_enum_ids_[udev_enum_id_] = true;
+  return reinterpret_cast<struct udev_enumerate*>(++udev_enum_id_);
+}
+
+int StandardGpioMockUdevInterface::EnumerateAddMatchSubsystem(
+    struct udev_enumerate* udev_enum, const char* subsystem) {
+  const size_t udev_enum_id = UdevEnumToId(udev_enum);
+  if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+    return -1;
+
+  // Ensure client is correctly requesting the GPIO subsystem.
+  EXPECT_STREQ(subsystem, kUdevGpioSubsystem);
+  if (strcmp(subsystem, kUdevGpioSubsystem))
+    return -1;
+
+  return 0;
+}
+
+int StandardGpioMockUdevInterface::EnumerateAddMatchSysname(
+    struct udev_enumerate* udev_enum, const char* sysname) {
+  const size_t udev_enum_id = UdevEnumToId(udev_enum);
+  if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+    return -1;
+
+  // Ensure client is requesting the correct sysnamem, depending on the correct
+  // phase in the detection process.
+  switch (udev_enum_id) {
+    case 0:  // looking for a GPIO chip
+      EXPECT_STREQ(sysname, kUdevGpioChipSysname);
+      if (strcmp(sysname, kUdevGpioChipSysname))
+        return -1;
+      break;
+    case 1:  // looking for dut_flaga/b
+    case 2: {
+      const int gpio_id = udev_enum_id - 1;
+      const std::string gpio_pattern =
+          StringPrintf("*%s", gpio_descriptors_[gpio_id].value);
+      EXPECT_STREQ(sysname, gpio_pattern.c_str());
+      if (strcmp(sysname, gpio_pattern.c_str()))
+        return -1;
+      break;
+    }
+    default:
+      ADD_FAILURE();  // can't get here
+      return -1;
+  }
+
+  return 0;
+}
+
+int StandardGpioMockUdevInterface::EnumerateScanDevices(
+    struct udev_enumerate* udev_enum) {
+  const size_t udev_enum_id = UdevEnumToId(udev_enum);
+  if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+    return -1;
+  return 0;  // nothing to do, really
+}
+
+struct udev_list_entry* StandardGpioMockUdevInterface::EnumerateGetListEntry(
+    struct udev_enumerate* udev_enum) {
+  const size_t udev_enum_id = UdevEnumToId(udev_enum);
+  if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+    return NULL;
+
+  // Return a list of devices corresponding to the provided enumeration.
+  switch (udev_enum_id) {
+    case 0:
+      return reinterpret_cast<struct udev_list_entry*>(
+          enum_gpio_chip_dev_list_);
+    case 1:
+      return reinterpret_cast<struct udev_list_entry*>(
+          enum_dutflaga_gpio_dev_list_);
+    case 2:
+      return reinterpret_cast<struct udev_list_entry*>(
+          enum_dutflagb_gpio_dev_list_);
+  }
+
+  ADD_FAILURE();  // can't get here
+  return NULL;
+}
+
+void StandardGpioMockUdevInterface::EnumerateUnref(
+    struct udev_enumerate* udev_enum) {
+  const size_t udev_enum_id = UdevEnumToId(udev_enum);
+  if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+    return;
+  used_udev_enum_ids_[udev_enum_id] = false;  // make sure it's freed just once
+}
+
+struct udev* StandardGpioMockUdevInterface::New() {
+  // The returned "pointer" is cast from an integer identifier.
+  EXPECT_LT(udev_id_, MAX_UDEV_OBJECTS);
+  if (udev_id_ >= MAX_UDEV_OBJECTS)
+    return NULL;
+  used_udev_ids_[udev_id_] = true;
+  return reinterpret_cast<struct udev*>(++udev_id_);
+}
+
+void StandardGpioMockUdevInterface::Unref(struct udev* udev) {
+  // Convert to object identifier, ensuring the object has been "allocated".
+  const size_t udev_id = UdevToId(udev);
+  if (udev_id >= MAX_UDEV_OBJECTS)
+    return;
+  used_udev_ids_[udev_id] = false;  // make sure it's freed just once
+}
+
+void StandardGpioMockUdevInterface::ExpectAllResourcesDeallocated() const {
+  // Make sure that all handles were released.
+  for (size_t i = 0; i < MAX_UDEV_OBJECTS; i++)
+    EXPECT_FALSE(used_udev_ids_[i]);
+  for (size_t i = 0; i < MAX_UDEV_ENUM_OBJECTS; i++)
+    EXPECT_FALSE(used_udev_enum_ids_[i]);
+  EXPECT_FALSE(gpio_chip_dev_.is_used);
+  EXPECT_FALSE(dutflaga_gpio_dev_.is_used);
+  EXPECT_FALSE(dutflagb_gpio_dev_.is_used);
+}
+
+void StandardGpioMockUdevInterface::ExpectDiscoverySuccess() const {
+  EXPECT_EQ(num_discovered_, kMockGpioIdMax);
+}
+
+void StandardGpioMockUdevInterface::ExpectDiscoveryFail() const {
+  EXPECT_LT(num_discovered_, kMockGpioIdMax);
+}
+
+StandardGpioMockUdevInterface::MockDevice*
+StandardGpioMockUdevInterface::UdevDeviceToMock(
+    struct udev_device* udev_dev) const {
+  EXPECT_TRUE(udev_dev);
+  if (!udev_dev)
+    return NULL;
+  MockDevice* mock_dev = reinterpret_cast<MockDevice*>(udev_dev);
+  EXPECT_TRUE(mock_dev->is_used);
+  if (!mock_dev->is_used)
+    return NULL;
+  return mock_dev;
+}
+
+size_t StandardGpioMockUdevInterface::UdevEnumToId(
+    struct udev_enumerate* udev_enum) const {
+  EXPECT_TRUE(udev_enum);
+  size_t udev_enum_id = reinterpret_cast<size_t>(udev_enum) - 1;
+  EXPECT_LT(udev_enum_id, MAX_UDEV_ENUM_OBJECTS);
+  EXPECT_TRUE(used_udev_enum_ids_[udev_enum_id]);
+  if (!(udev_enum && udev_enum_id < MAX_UDEV_ENUM_OBJECTS &&
+        used_udev_enum_ids_[udev_enum_id]))
+    return MAX_UDEV_ENUM_OBJECTS;
+  return udev_enum_id;
+}
+
+size_t StandardGpioMockUdevInterface::UdevToId(struct udev* udev) const {
+  EXPECT_TRUE(udev);
+  size_t udev_id = reinterpret_cast<size_t>(udev) - 1;
+  EXPECT_LT(udev_id, MAX_UDEV_OBJECTS);
+  EXPECT_TRUE(used_udev_ids_[udev_id]);
+  if (!(udev && udev_id < MAX_UDEV_OBJECTS && used_udev_ids_[udev_id]))
+    return MAX_UDEV_OBJECTS;
+  return udev_id;
+}
+
+#define MOCK_GPIO_CHIP1_ID     "200"
+#define MOCK_GPIO_CHIP2_ID     "201"
+
+const char* MultiChipGpioMockUdevInterface::enum_gpio_chip_dev_list_[] = {
+  "gpiochip" MOCK_GPIO_CHIP1_ID,
+  "gpiochip" MOCK_GPIO_CHIP2_ID,
+  NULL
+};
+
+MultiChipGpioMockUdevInterface::MultiChipGpioMockUdevInterface() {
+  gpio_chip1_dev_ = (MockDevice) {
+    MOCK_SYSFS_PREFIX "/gpiochip" MOCK_GPIO_CHIP1_ID,
+    gpio_descriptors_, kMockGpioIdMax, false, false
+  };
+  gpio_chip2_dev_ = (MockDevice) {
+    MOCK_SYSFS_PREFIX "/gpiochip" MOCK_GPIO_CHIP2_ID,
+    gpio_descriptors_, kMockGpioIdMax, false, false
+  };
+}
+
+struct udev_device* MultiChipGpioMockUdevInterface::DeviceNewFromSyspath(
+    struct udev* udev, const char* syspath) {
+  const size_t udev_id = UdevToId(udev);
+  if (udev_id >= MAX_UDEV_OBJECTS)
+    return NULL;
+  MockDevice* mock_dev;
+
+  // Generate the desired mock device based on the provided syspath.
+  if (!strcmp(syspath, enum_gpio_chip_dev_list_[0])) {
+    mock_dev = const_cast<MockDevice*>(&gpio_chip1_dev_);
+  } else if (!strcmp(syspath, enum_gpio_chip_dev_list_[1])) {
+    mock_dev = const_cast<MockDevice*>(&gpio_chip2_dev_);
+  } else if (!strcmp(syspath, enum_dutflaga_gpio_dev_list_[0])) {
+    mock_dev = const_cast<MockDevice*>(&dutflaga_gpio_dev_);
+  } else if (!strcmp(syspath, enum_dutflagb_gpio_dev_list_[0])) {
+    mock_dev = const_cast<MockDevice*>(&dutflagb_gpio_dev_);
+  } else {
+    ADD_FAILURE();
+    return NULL;
+  }
+
+  EXPECT_FALSE(mock_dev->is_used);
+  if (mock_dev->is_used)
+    return NULL;
+  mock_dev->is_used = true;
+
+  return reinterpret_cast<struct udev_device*>(mock_dev);
+}
+
+struct udev_list_entry* MultiChipGpioMockUdevInterface::EnumerateGetListEntry(
+    struct udev_enumerate* udev_enum) {
+  const size_t udev_enum_id = UdevEnumToId(udev_enum);
+
+  // Return a list of devices corresponding to the provided enumeration.
+  switch (udev_enum_id) {
+    case 0:
+      return reinterpret_cast<struct udev_list_entry*>(
+          enum_gpio_chip_dev_list_);
+    case 1:
+      return reinterpret_cast<struct udev_list_entry*>(
+          enum_dutflaga_gpio_dev_list_);
+    case 2:
+      return reinterpret_cast<struct udev_list_entry*>(
+          enum_dutflagb_gpio_dev_list_);
+  }
+
+  ADD_FAILURE();  // can't get here
+  return NULL;
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/gpio_mock_udev_interface.h b/gpio_mock_udev_interface.h
new file mode 100644
index 0000000..724a0f4
--- /dev/null
+++ b/gpio_mock_udev_interface.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_MOCK_UDEV_INTERFACE_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_MOCK_UDEV_INTERFACE_H__
+
+#include "update_engine/gpio_handler.h"
+#include "update_engine/udev_interface.h"
+
+// A set of mock udev interfaces for unit testing of GPIO handler.
+
+// Constant defining number of allowable mock udev objects.
+#define MAX_UDEV_OBJECTS      1
+#define MAX_UDEV_ENUM_OBJECTS 3
+
+namespace chromeos_update_engine {
+
+// An abstract class providing some diagnostic methods for testing.
+class GpioMockUdevInterface : public UdevInterface {
+ public:
+  // Asserts that all resources have been properly deallocated.
+  virtual void ExpectAllResourcesDeallocated() const = 0;
+  // Asserts the the udev client has successfully discovered the syspath of the
+  // GPIO signals.
+  virtual void ExpectDiscoverySuccess() const = 0;
+  // Asserts the opposite.
+  virtual void ExpectDiscoveryFail() const = 0;
+};
+
+class StandardGpioMockUdevInterface : public GpioMockUdevInterface {
+ public:
+  // Default constructor.
+  StandardGpioMockUdevInterface();
+
+  // Inherited interface methods.
+  virtual const char* ListEntryGetName(struct udev_list_entry* list_entry);
+  virtual udev_list_entry* ListEntryGetNext(struct udev_list_entry* list_entry);
+
+  virtual struct udev_device* DeviceNewFromSyspath(struct udev* udev,
+                                                   const char* syspath);
+  virtual const char* DeviceGetPropertyValue(struct udev_device* udev_device,
+                                             const char* key);
+  virtual const char* DeviceGetSyspath(struct udev_device* udev_device);
+  virtual void DeviceUnref(struct udev_device* udev_device);
+
+  virtual struct udev_enumerate* EnumerateNew(struct udev* udev);
+  virtual int EnumerateAddMatchSubsystem(struct udev_enumerate* udev_enum,
+                                         const char* subsystem);
+  virtual int EnumerateAddMatchSysname(struct udev_enumerate* udev_enum,
+                                       const char* sysname);
+  virtual int EnumerateScanDevices(struct udev_enumerate* udev_enum);
+  virtual struct udev_list_entry* EnumerateGetListEntry(
+      struct udev_enumerate* udev_enum);
+  virtual void EnumerateUnref(struct udev_enumerate* udev_enum);
+
+  virtual struct udev* New();
+  virtual void Unref(struct udev* udev);
+
+  virtual void ExpectAllResourcesDeallocated() const;
+  virtual void ExpectDiscoverySuccess() const;
+  virtual void ExpectDiscoveryFail() const;
+
+ protected:
+  // Some constants.
+  static const char* kUdevGpioSubsystem;
+  static const char* kUdevGpioChipSysname;
+  static const char* kMockGpioSysfsRoot;
+
+  // GPIO descriptor lookup.
+  struct GpioDescriptor {
+    const char* property;
+    const char* value;
+  };
+  static const GpioDescriptor gpio_descriptors_[];
+
+  // Numeric identifiers for new udev and enumerate objects.
+  size_t udev_id_;
+  size_t udev_enum_id_;
+
+  // Null-terminated lists of devices returned by various enumerations.
+  static const char* enum_gpio_chip_dev_list_[];
+  static const char* enum_dutflaga_gpio_dev_list_[];
+  static const char* enum_dutflagb_gpio_dev_list_[];
+
+  // Mock devices to be used during GPIO discovery.  These contain the syspath
+  // and a set of properties that the device may contain.
+  struct MockDevice {
+    const char* syspath;
+    const GpioDescriptor* properties;
+    size_t num_properties;
+    bool is_gpio;
+    bool is_used;
+  };
+  MockDevice gpio_chip_dev_;
+  MockDevice dutflaga_gpio_dev_;
+  MockDevice dutflagb_gpio_dev_;
+
+  // Tracking active mock object handles.
+  bool used_udev_ids_[MAX_UDEV_OBJECTS];
+  bool used_udev_enum_ids_[MAX_UDEV_ENUM_OBJECTS];
+
+  // Track discovery progress of GPIO signals.
+  unsigned num_discovered_;
+
+  // Convert mock udev handles into internal handles, with sanity checks.
+  MockDevice* UdevDeviceToMock(struct udev_device* udev_dev) const;
+  size_t UdevEnumToId(struct udev_enumerate* udev_enum) const;
+  size_t UdevToId(struct udev* udev) const;
+};
+
+class MultiChipGpioMockUdevInterface : public StandardGpioMockUdevInterface {
+ public:
+  // Default constructor.
+  MultiChipGpioMockUdevInterface();
+
+ protected:
+  virtual struct udev_device* DeviceNewFromSyspath(struct udev* udev,
+                                                   const char* syspath);
+  virtual struct udev_list_entry* EnumerateGetListEntry(
+      struct udev_enumerate* udev_enum);
+
+  // Null-terminated lists of devices returned by various enumerations.
+  static const char* enum_gpio_chip_dev_list_[];
+
+  // Mock devices to be used during GPIO discovery.  These contain the syspath
+  // and a set of properties that the device may contain.
+  MockDevice gpio_chip1_dev_;
+  MockDevice gpio_chip2_dev_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_MOCK_UDEV_INTERFACE_H__
diff --git a/main.cc b/main.cc
index 790fe73..fc0319f 100644
--- a/main.cc
+++ b/main.cc
@@ -183,7 +183,8 @@
   chromeos_update_engine::ConcreteDbusGlib dbus;
   chromeos_update_engine::UpdateAttempter update_attempter(&prefs,
                                                            &metrics_lib,
-                                                           &dbus);
+                                                           &dbus,
+                                                           NULL);
 
   // Create the dbus service object:
   dbus_g_object_type_install_info(UPDATE_ENGINE_TYPE_SERVICE,
@@ -195,7 +196,8 @@
   chromeos_update_engine::SetupDbusService(service);
 
   // Schedule periodic update checks.
-  chromeos_update_engine::UpdateCheckScheduler scheduler(&update_attempter);
+  chromeos_update_engine::UpdateCheckScheduler scheduler(&update_attempter,
+                                                         NULL);
   scheduler.Run();
 
   // Update boot flags after 45 seconds.
diff --git a/udev_interface.h b/udev_interface.h
new file mode 100644
index 0000000..b9765a8
--- /dev/null
+++ b/udev_interface.h
@@ -0,0 +1,193 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_UDEV_INTERFACE_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_UDEV_INTERFACE_H__
+
+#include <libudev.h>
+
+#include "update_engine/utils.h"
+
+// An interface for libudev calls, allowing to easily mock it.
+
+namespace chromeos_update_engine {
+
+// An interface for libudev methods that are being used in update engine.
+//
+// TODO(garnold) As is, this is a pretty lame indirection layer that otherwise
+// does not provide any better abstraction than the existing libudev API. Done
+// properly, we should replace it with encapsulated udev, enumerate and device
+// objects, and hide initialization, reference handling and iterators in ways
+// more appropriate to an object-oriented setting...
+class UdevInterface {
+ public:
+  // Interfaces for various udev closers. All of these are merely containers for
+  // a single pointer to some udev handle, which invoke the provided interface's
+  // unref method and nullify the handle upon destruction. It should suffice for
+  // derivative (concrete) interfaces to implement the various unref methods to
+  // fit their needs, making these closers behave as expected.
+  class UdevCloser {
+   public:
+    explicit UdevCloser(UdevInterface* udev_iface, struct udev** udev_p)
+        : udev_iface_(udev_iface), udev_p_(udev_p) {
+      CHECK(udev_iface && udev_p);
+    }
+    virtual ~UdevCloser() {
+      if (*udev_p_) {
+        udev_iface_->Unref(*udev_p_);
+        *udev_p_ = NULL;
+      }
+    }
+   protected:
+    UdevInterface* udev_iface_;
+    struct udev** udev_p_;
+   private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(UdevCloser);
+  };
+
+  class UdevEnumerateCloser {
+   public:
+    explicit UdevEnumerateCloser(UdevInterface* udev_iface,
+                                 struct udev_enumerate** udev_enum_p)
+        : udev_iface_(udev_iface), udev_enum_p_(udev_enum_p) {
+      CHECK(udev_iface && udev_enum_p);
+    }
+    virtual ~UdevEnumerateCloser() {
+      if (*udev_enum_p_) {
+        udev_iface_->EnumerateUnref(*udev_enum_p_);
+        *udev_enum_p_ = NULL;
+      }
+    }
+   protected:
+    UdevInterface* udev_iface_;
+    struct udev_enumerate** udev_enum_p_;
+   private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(UdevEnumerateCloser);
+  };
+
+  class UdevDeviceCloser {
+   public:
+    explicit UdevDeviceCloser(UdevInterface* udev_iface,
+                              struct udev_device** udev_dev_p)
+        : udev_iface_(udev_iface), udev_dev_p_(udev_dev_p) {
+      CHECK(udev_iface && udev_dev_p);
+    }
+    virtual ~UdevDeviceCloser() {
+      if (*udev_dev_p_) {
+        udev_iface_->DeviceUnref(*udev_dev_p_);
+        *udev_dev_p_ = NULL;
+      }
+    }
+   protected:
+    UdevInterface* udev_iface_;
+    struct udev_device** udev_dev_p_;
+   private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(UdevDeviceCloser);
+  };
+
+  virtual UdevCloser* NewUdevCloser(struct udev** udev_p) {
+    return new UdevCloser(this, udev_p);
+  }
+  virtual UdevEnumerateCloser* NewUdevEnumerateCloser(
+      struct udev_enumerate** udev_enum_p) {
+    return new UdevEnumerateCloser(this, udev_enum_p);
+  }
+  virtual UdevDeviceCloser* NewUdevDeviceCloser(
+      struct udev_device** udev_dev_p) {
+    return new UdevDeviceCloser(this, udev_dev_p);
+  }
+
+  // Lists.
+  virtual const char* ListEntryGetName(struct udev_list_entry* list_entry) = 0;
+  virtual udev_list_entry* ListEntryGetNext(
+      struct udev_list_entry* list_entry) = 0;
+
+  // Device methods.
+  virtual struct udev_device* DeviceNewFromSyspath(
+      struct udev* udev,
+      const char* syspath) = 0;
+  virtual const char* DeviceGetPropertyValue(struct udev_device* udev_device,
+                                             const char* key) = 0;
+  virtual const char* DeviceGetSyspath(
+      struct udev_device* udev_device) = 0;
+  virtual void DeviceUnref(struct udev_device* udev_device) = 0;
+
+  // Enumerate methods.
+  virtual struct udev_enumerate* EnumerateNew(struct udev* udev) = 0;
+  virtual int EnumerateAddMatchSubsystem(struct udev_enumerate* udev_enum,
+                                         const char* subsystem) = 0;
+  virtual int EnumerateAddMatchSysname(struct udev_enumerate* udev_enum,
+                                       const char* sysname) = 0;
+  virtual int EnumerateScanDevices(struct udev_enumerate* udev_enum) = 0;
+  virtual struct udev_list_entry* EnumerateGetListEntry(
+      struct udev_enumerate* udev_enum) = 0;
+  virtual void EnumerateUnref(struct udev_enumerate* udev_enum) = 0;
+
+  // Udev instance methods.
+  virtual struct udev* New() = 0;
+  virtual void Unref(struct udev* udev) = 0;
+};
+
+
+// Implementation of libudev interface using concrete udev calls.
+class StandardUdevInterface : public UdevInterface {
+ public:
+  // Concrete udev API wrappers utilizing the standard libudev calls.
+  virtual const char* ListEntryGetName(struct udev_list_entry* list_entry) {
+    return udev_list_entry_get_name(list_entry);
+  }
+  virtual struct udev_list_entry* ListEntryGetNext(
+      struct udev_list_entry* list_entry) {
+    return udev_list_entry_get_next(list_entry);
+  }
+
+  virtual struct udev_device* DeviceNewFromSyspath(struct udev* udev,
+                                                   const char* syspath) {
+    return udev_device_new_from_syspath(udev, syspath);
+  }
+  virtual const char* DeviceGetPropertyValue(struct udev_device* udev_device,
+                                             const char* key) {
+    return udev_device_get_property_value(udev_device, key);
+  }
+  virtual const char* DeviceGetSyspath(struct udev_device* udev_device) {
+    return udev_device_get_syspath(udev_device);
+  }
+  virtual void DeviceUnref(struct udev_device* udev_device) {
+    return udev_device_unref(udev_device);
+  }
+
+  virtual struct udev_enumerate* EnumerateNew(struct udev* udev) {
+    return udev_enumerate_new(udev);
+  }
+  virtual int EnumerateAddMatchSubsystem(struct udev_enumerate* udev_enum,
+                                         const char* subsystem) {
+    return udev_enumerate_add_match_subsystem(udev_enum, subsystem);
+  }
+  virtual int EnumerateAddMatchSysname(struct udev_enumerate* udev_enum,
+                                       const char* sysname) {
+    return udev_enumerate_add_match_sysname(udev_enum, sysname);
+  }
+  virtual int EnumerateScanDevices(struct udev_enumerate* udev_enum) {
+    return udev_enumerate_scan_devices(udev_enum);
+  }
+  virtual struct udev_list_entry* EnumerateGetListEntry(
+      struct udev_enumerate* udev_enum) {
+    return udev_enumerate_get_list_entry(udev_enum);
+  }
+  virtual void EnumerateUnref(struct udev_enumerate* udev_enum) {
+    return udev_enumerate_unref(udev_enum);
+  }
+
+  virtual struct udev* New() {
+    return udev_new();
+  }
+  virtual void Unref(struct udev* udev) {
+    return udev_unref(udev);
+  }
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_UDEV_INTERFACE_H__
+
diff --git a/update_attempter.cc b/update_attempter.cc
index 214f21a..d6c5496 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -106,7 +106,8 @@
 
 UpdateAttempter::UpdateAttempter(PrefsInterface* prefs,
                                  MetricsLibraryInterface* metrics_lib,
-                                 DbusGlibInterface* dbus_iface)
+                                 DbusGlibInterface* dbus_iface,
+                                 GpioHandler* gpio_handler)
     : processor_(new ActionProcessor()),
       dbus_service_(NULL),
       prefs_(prefs),
@@ -129,7 +130,9 @@
       update_boot_flags_running_(false),
       start_action_processor_(false),
       policy_provider_(NULL),
-      is_using_test_url_(false) {
+      is_using_test_url_(false),
+      is_test_update_attempted_(false),
+      gpio_handler_(gpio_handler) {
   if (utils::FileExists(kUpdateCompletedMarker))
     status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
 }
@@ -331,11 +334,11 @@
   // Read GPIO signals and determine whether this is an automated test scenario.
   // For safety, we only allow a test update to be performed once; subsequent
   // update requests will be carried out normally.
-  static bool is_test_used_once = false;
-  bool is_test = !is_test_used_once && GpioHandler::IsGpioSignalingTest();
+  bool is_test = (!is_test_update_attempted_ && gpio_handler_ &&
+                  gpio_handler_->IsTestModeSignaled());
   if (is_test) {
     LOG(INFO) << "test mode signaled";
-    is_test_used_once = true;
+    is_test_update_attempted_ = true;
   }
 
   Update(app_version, omaha_url, true, true, is_test);
diff --git a/update_attempter.h b/update_attempter.h
index 4f30667..950163e 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -61,7 +61,8 @@
 
   UpdateAttempter(PrefsInterface* prefs,
                   MetricsLibraryInterface* metrics_lib,
-                  DbusGlibInterface* dbus_iface);
+                  DbusGlibInterface* dbus_iface,
+                  GpioHandler* gpio_handler);
   virtual ~UpdateAttempter();
 
   // Checks for update and, if a newer version is available, attempts to update
@@ -308,6 +309,12 @@
   // A flag for indicating whether we are using a test server URL.
   bool is_using_test_url_;
 
+  // A flag indicating whether a test update cycle was already attempted.
+  bool is_test_update_attempted_;
+
+  // GPIO handler object.
+  GpioHandler* gpio_handler_;
+
   DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
 };
 
diff --git a/update_attempter_mock.h b/update_attempter_mock.h
index 50b5c25..bdb83ff 100644
--- a/update_attempter_mock.h
+++ b/update_attempter_mock.h
@@ -15,7 +15,7 @@
 class UpdateAttempterMock : public UpdateAttempter {
  public:
   explicit UpdateAttempterMock(MockDbusGlib* dbus)
-      : UpdateAttempter(NULL, NULL, dbus) {}
+      : UpdateAttempter(NULL, NULL, dbus, NULL) {}
 
   MOCK_METHOD5(Update, void(const std::string& app_version,
                             const std::string& omaha_url,
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 6402bb6..04648e3 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -36,7 +36,7 @@
 class UpdateAttempterUnderTest : public UpdateAttempter {
  public:
   explicit UpdateAttempterUnderTest(MockDbusGlib* dbus)
-      : UpdateAttempter(NULL, NULL, dbus) {}
+      : UpdateAttempter(NULL, NULL, dbus, NULL) {}
 };
 
 class UpdateAttempterTest : public ::testing::Test {
@@ -120,7 +120,7 @@
   OmahaResponse response;
   response.poll_interval = 234;
   action.SetOutputObject(response);
-  UpdateCheckScheduler scheduler(&attempter_);
+  UpdateCheckScheduler scheduler(&attempter_, NULL);
   attempter_.set_update_check_scheduler(&scheduler);
   EXPECT_CALL(prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
   attempter_.ActionCompleted(NULL, &action, kActionCodeSuccess);
@@ -359,7 +359,7 @@
 }
 
 TEST_F(UpdateAttempterTest, PingOmahaTest) {
-  UpdateCheckScheduler scheduler(&attempter_);
+  UpdateCheckScheduler scheduler(&attempter_, NULL);
   scheduler.enabled_ = true;
   EXPECT_FALSE(scheduler.scheduled_);
   attempter_.set_update_check_scheduler(&scheduler);
diff --git a/update_check_scheduler.cc b/update_check_scheduler.cc
index cbe90c4..c3b0d17 100644
--- a/update_check_scheduler.cc
+++ b/update_check_scheduler.cc
@@ -18,12 +18,15 @@
 const int UpdateCheckScheduler::kTimeoutMaxBackoffInterval =  4 * 60 * 60;
 const int UpdateCheckScheduler::kTimeoutRegularFuzz        = 10 * 60;
 
-UpdateCheckScheduler::UpdateCheckScheduler(UpdateAttempter* update_attempter)
+UpdateCheckScheduler::UpdateCheckScheduler(UpdateAttempter* update_attempter,
+                                           GpioHandler* gpio_handler)
     : update_attempter_(update_attempter),
       enabled_(false),
       scheduled_(false),
       last_interval_(0),
-      poll_interval_(0) {}
+      poll_interval_(0),
+      is_test_update_attempted_(false),
+      gpio_handler_(gpio_handler) {}
 
 UpdateCheckScheduler::~UpdateCheckScheduler() {}
 
@@ -88,14 +91,15 @@
   CHECK(me->scheduled_);
   me->scheduled_ = false;
 
-  static bool is_test_used_once = false;
   bool is_test = false;
   if (me->IsOOBEComplete() ||
-      (is_test = !is_test_used_once && GpioHandler::IsGpioSignalingTest())) {
+      (is_test = (!me->is_test_update_attempted_ &&
+                  me->gpio_handler_ &&
+                  me->gpio_handler_->IsTestModeSignaled()))) {
     if (is_test) {
       LOG(WARNING)
           << "test mode signaled, allowing update check prior to OOBE complete";
-      is_test_used_once = true;
+      me->is_test_update_attempted_ = true;
     }
 
     // Before updating, we flush any previously generated UMA reports.
diff --git a/update_check_scheduler.h b/update_check_scheduler.h
index edfbbfd..cd3ad87 100644
--- a/update_check_scheduler.h
+++ b/update_check_scheduler.h
@@ -41,7 +41,8 @@
   static const int kTimeoutRegularFuzz;
   static const int kTimeoutMaxBackoffInterval;
 
-  UpdateCheckScheduler(UpdateAttempter* update_attempter);
+  UpdateCheckScheduler(UpdateAttempter* update_attempter,
+                       GpioHandler* gpio_handler);
   virtual ~UpdateCheckScheduler();
 
   // Initiates the periodic update checks, if necessary.
@@ -128,6 +129,12 @@
   // Server dictated poll interval in seconds, if positive.
   int poll_interval_;
 
+  // A flag indicating whether a test update cycle was already attempted.
+  bool is_test_update_attempted_;
+
+  // GPIO handler object.
+  GpioHandler* gpio_handler_;
+
   DISALLOW_COPY_AND_ASSIGN(UpdateCheckScheduler);
 };
 
diff --git a/update_check_scheduler_unittest.cc b/update_check_scheduler_unittest.cc
index 098651b..965ce9d 100644
--- a/update_check_scheduler_unittest.cc
+++ b/update_check_scheduler_unittest.cc
@@ -30,7 +30,7 @@
 class UpdateCheckSchedulerUnderTest : public UpdateCheckScheduler {
  public:
   UpdateCheckSchedulerUnderTest(UpdateAttempter* update_attempter)
-      : UpdateCheckScheduler(update_attempter) {}
+      : UpdateCheckScheduler(update_attempter, NULL) {}
 
   MOCK_METHOD2(GTimeoutAddSeconds, guint(guint seconds, GSourceFunc function));
   MOCK_METHOD0(IsBootDeviceRemovable, bool());