// +build linux

/*
Copyright 2015 The Kubernetes Authors.

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.
*/

package oom

import (
	"os"

	"github.com/opencontainers/runc/libcontainer/cgroups"
	"testing"

	"github.com/stretchr/testify/assert"
)

// Converts a sequence of PID lists into a PID lister.
// The PID lister returns pidListSequence[i] on the ith call. If i >= length of pidListSequence
// then return the last element of pidListSequence (the sequence is considered to have) stabilized.
func sequenceToPidLister(pidListSequence [][]int) func(string) ([]int, error) {
	var numCalls int
	return func(cgroupName string) ([]int, error) {
		numCalls++
		if len(pidListSequence) == 0 {
			return []int{}, nil
		} else if numCalls > len(pidListSequence) {
			return pidListSequence[len(pidListSequence)-1], nil
		}
		return pidListSequence[numCalls-1], nil
	}
}

// Tests that applyOOMScoreAdjContainer correctly applies OOM scores to relevant processes, or
// returns the right error.
func applyOOMScoreAdjContainerTester(pidListSequence [][]int, maxTries int, appliedPids []int, expectedError bool, t *testing.T) {
	pidOOMs := make(map[int]bool)

	// Mock ApplyOOMScoreAdj and pidLister.
	oomAdjuster := NewOOMAdjuster()
	oomAdjuster.ApplyOOMScoreAdj = func(pid int, oomScoreAdj int) error {
		pidOOMs[pid] = true
		return nil
	}
	oomAdjuster.pidLister = sequenceToPidLister(pidListSequence)
	err := oomAdjuster.ApplyOOMScoreAdjContainer("", 100, maxTries)

	// Check error value.
	if expectedError && err == nil {
		t.Errorf("Expected error %+v when running ApplyOOMScoreAdjContainer but got no error", expectedError)
		return
	} else if !expectedError && err != nil {
		t.Errorf("Expected no error but got error %+v when running ApplyOOMScoreAdjContainer", err)
		return
	} else if err != nil {
		return
	}
	// Check that OOM scores were applied to the right processes.
	if len(appliedPids) != len(pidOOMs) {
		t.Errorf("Applied OOM scores to incorrect number of processes - %+v vs %v", appliedPids, pidOOMs)
		return
	}
	for _, pid := range appliedPids {
		if !pidOOMs[pid] {
			t.Errorf("Failed to apply OOM scores to process %d", pid)
		}
	}
}

func TestOOMScoreAdjContainer(t *testing.T) {
	pidListSequenceEmpty := [][]int{}
	applyOOMScoreAdjContainerTester(pidListSequenceEmpty, 3, nil, true, t)

	pidListSequence1 := [][]int{
		{1, 2},
	}
	applyOOMScoreAdjContainerTester(pidListSequence1, 1, []int{1, 2}, false, t)

	pidListSequenceLag := [][]int{
		{},
		{},
		{},
		{1, 2, 4},
	}
	for i := 1; i < 4; i++ {
		applyOOMScoreAdjContainerTester(pidListSequenceLag, i, nil, true, t)
	}
	applyOOMScoreAdjContainerTester(pidListSequenceLag, 4, []int{1, 2, 4}, false, t)
}

func TestPidListerFailure(t *testing.T) {
	_, err := getPids("/does/not/exist")
	assert.True(t, cgroups.IsNotFound(err) || os.IsNotExist(err), "expected getPids to return not exists error. Got %v", err)
}
