Radix cross Linux

The main Radix cross Linux repository contains the build scripts of packages, which have the most complete and common functionality for desktop machines

452 Commits   2 Branches   1 Tag
// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "subprocess.h"
#include "tokenpool.h"

#include "test.h"

#ifndef _WIN32
// SetWithLots need setrlimit.
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#endif

using namespace std;

namespace {

#ifdef _WIN32
const char* kSimpleCommand = "cmd /c dir \\";
#else
const char* kSimpleCommand = "ls /";
#endif

struct TestTokenPool : public TokenPool {
  bool Acquire()     { return false; }
  void Reserve()     {}
  void Release()     {}
  void Clear()       {}
  bool Setup(bool ignore_unused, bool verbose, double& max_load_average) { return false; }

#ifdef _WIN32
  bool _token_available;
  void WaitForTokenAvailability(HANDLE ioport) {
    if (_token_available)
      // unblock GetQueuedCompletionStatus()
      PostQueuedCompletionStatus(ioport, 0, (ULONG_PTR) this, NULL);
  }
  bool TokenIsAvailable(ULONG_PTR key) { return key == (ULONG_PTR) this; }
#else
  int _fd;
  int GetMonitorFd() { return _fd; }
#endif
};

struct SubprocessTest : public testing::Test {
  SubprocessSet subprocs_;
  TestTokenPool tokens_;
};

}  // anonymous namespace

// Run a command that fails and emits to stderr.
TEST_F(SubprocessTest, BadCommandStderr) {
  Subprocess* subproc = subprocs_.Add("cmd /c ninja_no_such_command");
  ASSERT_NE((Subprocess *) 0, subproc);

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    // Pretend we discovered that stderr was ready for writing.
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());

  EXPECT_EQ(ExitFailure, subproc->Finish());
  EXPECT_NE("", subproc->GetOutput());
}

// Run a command that does not exist
TEST_F(SubprocessTest, NoSuchCommand) {
  Subprocess* subproc = subprocs_.Add("ninja_no_such_command");
  ASSERT_NE((Subprocess *) 0, subproc);

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    // Pretend we discovered that stderr was ready for writing.
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());

  EXPECT_EQ(ExitFailure, subproc->Finish());
  EXPECT_NE("", subproc->GetOutput());
#ifdef _WIN32
  ASSERT_EQ("CreateProcess failed: The system cannot find the file "
            "specified.\n", subproc->GetOutput());
#endif
}

#ifndef _WIN32

TEST_F(SubprocessTest, InterruptChild) {
  Subprocess* subproc = subprocs_.Add("kill -INT $$");
  ASSERT_NE((Subprocess *) 0, subproc);

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());

  EXPECT_EQ(ExitInterrupted, subproc->Finish());
}

TEST_F(SubprocessTest, InterruptParent) {
  Subprocess* subproc = subprocs_.Add("kill -INT $PPID ; sleep 1");
  ASSERT_NE((Subprocess *) 0, subproc);

  while (!subproc->Done()) {
    bool interrupted = subprocs_.DoWork(NULL);
    if (interrupted)
      return;
  }

  ASSERT_FALSE("We should have been interrupted");
}

TEST_F(SubprocessTest, InterruptChildWithSigTerm) {
  Subprocess* subproc = subprocs_.Add("kill -TERM $$");
  ASSERT_NE((Subprocess *) 0, subproc);

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());

  EXPECT_EQ(ExitInterrupted, subproc->Finish());
}

TEST_F(SubprocessTest, InterruptParentWithSigTerm) {
  Subprocess* subproc = subprocs_.Add("kill -TERM $PPID ; sleep 1");
  ASSERT_NE((Subprocess *) 0, subproc);

  while (!subproc->Done()) {
    bool interrupted = subprocs_.DoWork(NULL);
    if (interrupted)
      return;
  }

  ASSERT_FALSE("We should have been interrupted");
}

TEST_F(SubprocessTest, InterruptChildWithSigHup) {
  Subprocess* subproc = subprocs_.Add("kill -HUP $$");
  ASSERT_NE((Subprocess *) 0, subproc);

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());

  EXPECT_EQ(ExitInterrupted, subproc->Finish());
}

TEST_F(SubprocessTest, InterruptParentWithSigHup) {
  Subprocess* subproc = subprocs_.Add("kill -HUP $PPID ; sleep 1");
  ASSERT_NE((Subprocess *) 0, subproc);

  while (!subproc->Done()) {
    bool interrupted = subprocs_.DoWork(NULL);
    if (interrupted)
      return;
  }

  ASSERT_FALSE("We should have been interrupted");
}

TEST_F(SubprocessTest, Console) {
  // Skip test if we don't have the console ourselves.
  if (isatty(0) && isatty(1) && isatty(2)) {
    Subprocess* subproc =
        subprocs_.Add("test -t 0 -a -t 1 -a -t 2", /*use_console=*/true);
    ASSERT_NE((Subprocess*)0, subproc);

    subprocs_.ResetTokenAvailable();
    while (!subproc->Done()) {
      subprocs_.DoWork(NULL);
    }
    ASSERT_FALSE(subprocs_.IsTokenAvailable());

    EXPECT_EQ(ExitSuccess, subproc->Finish());
  }
}

#endif

TEST_F(SubprocessTest, SetWithSingle) {
  Subprocess* subproc = subprocs_.Add(kSimpleCommand);
  ASSERT_NE((Subprocess *) 0, subproc);

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());
  ASSERT_EQ(ExitSuccess, subproc->Finish());
  ASSERT_NE("", subproc->GetOutput());

  ASSERT_EQ(1u, subprocs_.finished_.size());
}

TEST_F(SubprocessTest, SetWithMulti) {
  Subprocess* processes[3];
  const char* kCommands[3] = {
    kSimpleCommand,
#ifdef _WIN32
    "cmd /c echo hi",
    "cmd /c time /t",
#else
    "id -u",
    "pwd",
#endif
  };

  for (int i = 0; i < 3; ++i) {
    processes[i] = subprocs_.Add(kCommands[i]);
    ASSERT_NE((Subprocess *) 0, processes[i]);
  }

  ASSERT_EQ(3u, subprocs_.running_.size());
  for (int i = 0; i < 3; ++i) {
    ASSERT_FALSE(processes[i]->Done());
    ASSERT_EQ("", processes[i]->GetOutput());
  }

  subprocs_.ResetTokenAvailable();
  while (!processes[0]->Done() || !processes[1]->Done() ||
         !processes[2]->Done()) {
    ASSERT_GT(subprocs_.running_.size(), 0u);
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());
  ASSERT_EQ(0u, subprocs_.running_.size());
  ASSERT_EQ(3u, subprocs_.finished_.size());

  for (int i = 0; i < 3; ++i) {
    ASSERT_EQ(ExitSuccess, processes[i]->Finish());
    ASSERT_NE("", processes[i]->GetOutput());
    delete processes[i];
  }
}

#if defined(USE_PPOLL)
TEST_F(SubprocessTest, SetWithLots) {
  // Arbitrary big number; needs to be over 1024 to confirm we're no longer
  // hostage to pselect.
  const unsigned kNumProcs = 1025;

  // Make sure [ulimit -n] isn't going to stop us from working.
  rlimit rlim;
  ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlim));
  if (rlim.rlim_cur < kNumProcs) {
    printf("Raise [ulimit -n] above %u (currently %lu) to make this test go\n",
           kNumProcs, rlim.rlim_cur);
    return;
  }

  vector<Subprocess*> procs;
  for (size_t i = 0; i < kNumProcs; ++i) {
    Subprocess* subproc = subprocs_.Add("/bin/echo");
    ASSERT_NE((Subprocess *) 0, subproc);
    procs.push_back(subproc);
  }
  subprocs_.ResetTokenAvailable();
  while (!subprocs_.running_.empty())
    subprocs_.DoWork(NULL);
  ASSERT_FALSE(subprocs_.IsTokenAvailable());
  for (size_t i = 0; i < procs.size(); ++i) {
    ASSERT_EQ(ExitSuccess, procs[i]->Finish());
    ASSERT_NE("", procs[i]->GetOutput());
  }
  ASSERT_EQ(kNumProcs, subprocs_.finished_.size());
}
#endif  // !__APPLE__ && !_WIN32

// TODO: this test could work on Windows, just not sure how to simply
// read stdin.
#ifndef _WIN32
// Verify that a command that attempts to read stdin correctly thinks
// that stdin is closed.
TEST_F(SubprocessTest, ReadStdin) {
  Subprocess* subproc = subprocs_.Add("cat -");
  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    subprocs_.DoWork(NULL);
  }
  ASSERT_FALSE(subprocs_.IsTokenAvailable());
  ASSERT_EQ(ExitSuccess, subproc->Finish());
  ASSERT_EQ(1u, subprocs_.finished_.size());
}
#endif  // _WIN32

TEST_F(SubprocessTest, TokenAvailable) {
  Subprocess* subproc = subprocs_.Add(kSimpleCommand);
  ASSERT_NE((Subprocess *) 0, subproc);

  // simulate GNUmake jobserver pipe with 1 token
#ifdef _WIN32
  tokens_._token_available = true;
#else
  int fds[2];
  ASSERT_EQ(0u, pipe(fds));
  tokens_._fd = fds[0];
  ASSERT_EQ(1u, write(fds[1], "T", 1));
#endif

  subprocs_.ResetTokenAvailable();
  subprocs_.DoWork(&tokens_);
#ifdef _WIN32
  tokens_._token_available = false;
  // we need to loop here as we have no conrol where the token
  // I/O completion post ends up in the queue
  while (!subproc->Done() && !subprocs_.IsTokenAvailable()) {
    subprocs_.DoWork(&tokens_);
  }
#endif

  EXPECT_TRUE(subprocs_.IsTokenAvailable());
  EXPECT_EQ(0u, subprocs_.finished_.size());

  // remove token to let DoWork() wait for command again
#ifndef _WIN32
  char token;
  ASSERT_EQ(1u, read(fds[0], &token, 1));
#endif

  while (!subproc->Done()) {
    subprocs_.DoWork(&tokens_);
  }

#ifndef _WIN32
  close(fds[1]);
  close(fds[0]);
#endif

  EXPECT_EQ(ExitSuccess, subproc->Finish());
  EXPECT_NE("", subproc->GetOutput());

  EXPECT_EQ(1u, subprocs_.finished_.size());
}

TEST_F(SubprocessTest, TokenNotAvailable) {
  Subprocess* subproc = subprocs_.Add(kSimpleCommand);
  ASSERT_NE((Subprocess *) 0, subproc);

  // simulate GNUmake jobserver pipe with 0 tokens
#ifdef _WIN32
  tokens_._token_available = false;
#else
  int fds[2];
  ASSERT_EQ(0u, pipe(fds));
  tokens_._fd = fds[0];
#endif

  subprocs_.ResetTokenAvailable();
  while (!subproc->Done()) {
    subprocs_.DoWork(&tokens_);
  }

#ifndef _WIN32
  close(fds[1]);
  close(fds[0]);
#endif

  EXPECT_FALSE(subprocs_.IsTokenAvailable());
  EXPECT_EQ(ExitSuccess, subproc->Finish());
  EXPECT_NE("", subproc->GetOutput());

  EXPECT_EQ(1u, subprocs_.finished_.size());
}