/*
 * Copyright 2021 Bloomberg Finance LP
 *
 * 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 <gtest/gtest.h>

#include <buildboxcommon_executionstatsutils.h>
#include <buildboxcommon_systemutils.h>

#include <sys/resource.h>
#include <sys/types.h>
#include <unistd.h>

TEST(ExecutionStatsUtilsTests, TestProtoFromRusageStruct)
{
    const rusage r = rusage{.ru_utime = {.tv_sec = 2, .tv_usec = 22},
                            .ru_stime = {.tv_sec = 1, .tv_usec = 11},
                            .ru_maxrss = 1024,
                            .ru_minflt = 6,
                            .ru_majflt = 5,
                            .ru_inblock = 3,
                            .ru_oublock = 4,
                            .ru_nvcsw = 7,
                            .ru_nivcsw = 8};

    // build::buildbox::ExecutionStatistics_ProcessResourceUsage
    auto proto =
        buildboxcommon::ExecutionStatsUtils::processResourceUsageFromRusage(r);

    const long NANOS_PER_MICRO = 1000;

    // NOLINTBEGIN (cppcoreguidelines-pro-type-union-access)
    // struct_rusage.h requires the use of unions for ABI compatibility
    // reasons.
    ASSERT_EQ(proto.stime().seconds(), r.ru_stime.tv_sec);
    ASSERT_EQ(proto.stime().nanos(), r.ru_stime.tv_usec * NANOS_PER_MICRO);

    ASSERT_EQ(proto.utime().seconds(), r.ru_utime.tv_sec);
    ASSERT_EQ(proto.utime().nanos(), r.ru_utime.tv_usec * NANOS_PER_MICRO);

    ASSERT_EQ(proto.maxrss(), r.ru_maxrss);
    ASSERT_EQ(proto.inblock(), r.ru_inblock);
    ASSERT_EQ(proto.oublock(), r.ru_oublock);
    ASSERT_EQ(proto.majflt(), r.ru_majflt);
    ASSERT_EQ(proto.minflt(), r.ru_minflt);
    ASSERT_EQ(proto.nvcsw(), r.ru_nvcsw);
    ASSERT_EQ(proto.nivcsw(), r.ru_nivcsw);
    // NOLINTEND
}

TEST(ExecutionStatsUtilsTests, TestGetChildrenProcessUsage)
{
    const pid_t pid = fork();
    ASSERT_GE(pid, 0);

    if (pid == 0) { // Busy work to report a positive user CPU time.
        int res = 1;
        for (auto i = 0; i < 10000000; i++) {
            res ^= i;
        }

        _exit(res % 255);
    }
    else {
        ASSERT_GE(buildboxcommon::SystemUtils::waitPid(pid), 0);

        const build::buildbox::ExecutionStatistics execStatsProto =
            buildboxcommon::ExecutionStatsUtils::getChildrenProcessRusage();

        const auto &commandUsage = execStatsProto.command_rusage();

        EXPECT_GT(
            commandUsage.utime().seconds() + commandUsage.utime().nanos(), 0);

        EXPECT_GE(
            commandUsage.stime().seconds() + commandUsage.stime().nanos(), 0);
    }
}
