/*
 * Copyright (c) 2016 GitHub, Inc.
 *
 * 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 <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "catch.hpp"
#include "usdt.h"

#ifdef HAVE_SDT_HEADER
/* required to insert USDT probes on this very executable --
 * we're gonna be testing them live! */
#include <sys/sdt.h>

static int a_probed_function() {
  int an_int = 23 + getpid();
  void *a_pointer = malloc(4);
  DTRACE_PROBE2(libbcc_test, sample_probe_1, an_int, a_pointer);
  free(a_pointer);
  return an_int;
}

TEST_CASE("test finding a probe in our own process", "[usdt]") {
  USDT::Context ctx(getpid());
  REQUIRE(ctx.num_probes() >= 1);

  SECTION("our test probe") {
    auto probe = ctx.get("sample_probe_1");
    REQUIRE(probe);

    REQUIRE(probe->in_shared_object() == false);
    REQUIRE(probe->name() == "sample_probe_1");
    REQUIRE(probe->provider() == "libbcc_test");
    REQUIRE(probe->bin_path().find("/test_libbcc") != std::string::npos);

    REQUIRE(probe->num_locations() == 1);
    REQUIRE(probe->num_arguments() == 2);
    REQUIRE(probe->need_enable() == false);

    REQUIRE(a_probed_function() != 0);
  }
}
#endif  // HAVE_SDT_HEADER

class ChildProcess {
  pid_t pid_;

public:
  ChildProcess(const char *name, char *const argv[]) {
    pid_ = fork();
    if (pid_ == 0) {
      execvp(name, argv);
      exit(0);
    }
    if (spawned()) {
      usleep(250000);
      if (kill(pid_, 0) < 0)
        pid_ = -1;
    }
  }

  ~ChildProcess() {
    if (spawned()) {
      int status;
      kill(pid_, SIGKILL);
      if (waitpid(pid_, &status, 0) != pid_)
        abort();
    }
  }

  bool spawned() const { return pid_ > 0; }
  pid_t pid() const { return pid_; }
};

TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
  size_t mri_probe_count = 0;

  SECTION("without a running Ruby process") {
    USDT::Context ctx("ruby");

    if (!ctx.loaded())
      return;

    REQUIRE(ctx.num_probes() > 10);
    mri_probe_count = ctx.num_probes();

    SECTION("GC static probe") {
      auto probe = ctx.get("gc__mark__begin");
      REQUIRE(probe);

      REQUIRE(probe->in_shared_object() == true);
      REQUIRE(probe->name() == "gc__mark__begin");
      REQUIRE(probe->provider() == "ruby");
      REQUIRE(probe->bin_path().find("/ruby") != std::string::npos);

      REQUIRE(probe->num_locations() == 1);
      REQUIRE(probe->num_arguments() == 0);
      REQUIRE(probe->need_enable() == true);
    }

    SECTION("object creation probe") {
      auto probe = ctx.get("object__create");
      REQUIRE(probe);

      REQUIRE(probe->in_shared_object() == true);
      REQUIRE(probe->name() == "object__create");
      REQUIRE(probe->provider() == "ruby");
      REQUIRE(probe->bin_path().find("/ruby") != std::string::npos);

      REQUIRE(probe->num_locations() == 1);
      REQUIRE(probe->num_arguments() == 3);
      REQUIRE(probe->need_enable() == true);
    }

    SECTION("array creation probe") {
      auto probe = ctx.get("array__create");
      REQUIRE(probe);
      REQUIRE(probe->name() == "array__create");

      REQUIRE(probe->num_locations() == 7);
      REQUIRE(probe->num_arguments() == 3);
      REQUIRE(probe->need_enable() == true);
    }
  }

  SECTION("with a running Ruby process") {
    static char _ruby[] = "ruby";
    char *const argv[2] = {_ruby, NULL};

    ChildProcess ruby(argv[0], argv);
    if (!ruby.spawned())
      return;

    USDT::Context ctx(ruby.pid());
    REQUIRE(ctx.num_probes() >= mri_probe_count);

    SECTION("get probe in running process") {
      auto probe = ctx.get("gc__mark__begin");
      REQUIRE(probe);

      REQUIRE(probe->in_shared_object() == true);
      REQUIRE(probe->name() == "gc__mark__begin");
      REQUIRE(probe->provider() == "ruby");
      REQUIRE(probe->bin_path().find("/ruby") != std::string::npos);

      REQUIRE(probe->num_locations() == 1);
      REQUIRE(probe->num_arguments() == 0);
      REQUIRE(probe->need_enable() == true);
    }
  }
}
