SquirrelJME/SquirrelJME

View on GitHub
nanocoat/tests/testNvmLocalPopReference.c

Summary

Maintainability
Test Coverage
/* -*- Mode: C; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// -------------------------------------------------------------------------*/

#include <string.h>

#include "mock.h"
#include "proto.h"
#include "sjme/debug.h"
#include "sjme/nvmFunc.h"
#include "test.h"
#include "unit.h"

#define TEST_NUM_OBJECT_IDS 3

typedef struct testHookResult
{
    /** The number of GCed objects. */
    sjme_jint count;
    
    /** The GCed objects. */
    sjme_jobject gc[SJME_MOCK_MAX_OBJECTS];
} testHookResult;

static sjme_jboolean hookGcNvmLocalPopReference(sjme_nvm_frame* frame,
    sjme_jobject instance)
{
    sjme_mock* mock;
    testHookResult* hookResult;
    
    /* Debug. */
    sjme_message("GC of %p...", instance);
    
    /* Mock must be set. */
    mock = frame->inThread->inState->frontEnd.data;
    if (mock == NULL)
        return SJME_JNI_FALSE;
    
    /* There must be a hook result. */
    hookResult = mock->special;
    if (hookResult == NULL)
        return SJME_JNI_FALSE;
    
    /* Track it, within reason. */
    if (hookResult->count < SJME_MOCK_MAX_OBJECTS)
        hookResult->gc[hookResult->count++] = instance;
    
    /* Success! */
    return SJME_JNI_TRUE;
}
 
const sjme_nvm_stateHooks hooksNvmLocalPopReference =
{
    .gc = hookGcNvmLocalPopReference,
};

sjme_jboolean configNvmLocalPopReference(
    sjme_attrInNotNull sjme_mock* inState,
    sjme_attrInNotNull sjme_mock_configWork* inCurrent)
{
    sjme_mock_configDataNvmState* state;
    sjme_mock_configDataNvmFrame* frame;
    
    /* Check. */
    if (inState == NULL || inCurrent == NULL)
        return SJME_JNI_FALSE;
    
    /* Quick access. */
    state = &inCurrent->data.nvmState;
    frame = &inCurrent->data.nvmFrame;

    /* Configure. */
    switch (inCurrent->type)
    {
        case SJME_MOCK_DO_TYPE_NVM_STATE:
            state->hooks = &hooksNvmLocalPopReference;
            break;
        
        case SJME_MOCK_DO_TYPE_NVM_FRAME:
            frame->maxLocals = 1;
            frame->maxStack = 1;
            frame->treads[SJME_JAVA_TYPE_ID_OBJECT].max = 2;
            frame->treads[SJME_JAVA_TYPE_ID_OBJECT].stackBaseIndex = 1;
            break;
    }
    
    return SJME_JNI_TRUE;
}

/** Mock set for test. */
static const sjme_mock_configSet mockNvmLocalPopReference =
    {
        configNvmLocalPopReference,
        0,

        /* Mock calls. */
        {
            sjme_mock_doNvmState,
            sjme_mock_doNvmThread,
            sjme_mock_doNvmFrame,
            sjme_mock_doNvmObject,
            sjme_mock_doNvmObject,
            NULL
        }
};

SJME_TEST_DECLARE(testNvmLocalPopReference)
{
    sjme_jbyte firstId, secondId;
    sjme_mock state;
    sjme_nvm_frame* frame;
    sjme_jint oldNumStack;
    sjme_nvm_frameTread* objectsTread;
    sjme_nvm_frameStack* stack;
    testHookResult hookResult;
    
    /* Test all possible combination of objects: [a, b, NULl]. */
    /* This is for testing that reference counting works in this case. */
    for (firstId = 0; firstId < TEST_NUM_OBJECT_IDS; firstId++)
        for (secondId = 0; secondId < TEST_NUM_OBJECT_IDS; secondId++)
        {
            /* Perform the mock. */
            memset(&state, 0, sizeof(state));
            if (!sjme_mock_act(test, &state,
                    &mockNvmLocalPopReference,
                    firstId + (secondId * TEST_NUM_OBJECT_IDS)))
                sjme_die("Invalid mock");
            
            /* Set special data for testing. */
            memset(&hookResult, 0, sizeof(hookResult));
            state.special = &hookResult;
        
            /* Get initialize frame size. */
            frame = state.threads[0].nvmThread->top;
    
            /* Setup integer values. */
            objectsTread = frame->treads[SJME_JAVA_TYPE_ID_OBJECT];
            stack = frame->stack;
            objectsTread->values.jobjects[0] = state.objects[secondId];
            objectsTread->values.jobjects[1] = state.objects[firstId];
            objectsTread->count = objectsTread->stackBaseIndex + 1;
            stack->count = 1;
            stack->order[0] = SJME_JAVA_TYPE_ID_OBJECT;
    
            /* Pop integer from the stack to the first local. */
            oldNumStack = stack->count;
            if (!sjme_nvm_localPopReference(frame, 0))
                return sjme_unit_fail(test, "Failed to pop local reference.");
            
            /* Only a specific object should be GCed and only in a certain */
            /* circumstance. */
            if (state.objects[secondId] != NULL &&
                state.objects[secondId] != state.objects[firstId])
            {
                sjme_unit_equalL(test,
                    hookResult.gc[0], state.objects[secondId],
                    "Old local was not what should have been GCed?");
                sjme_unit_equalI(test,
                    hookResult.count, 1,
                    "Different old local not GCed?");
            }
    
            /* New stack should be lower. */
            sjme_unit_equalI(test, stack->count, oldNumStack - 1,
                "Items in stack not lower?");
    
            /* Check that the value was moved over. */
            sjme_unit_equalL(test, state.objects[firstId],
                objectsTread->values.jobjects[0],
                "Popped stack into local was not the correct value.");
        
            /* And the stack value was cleared. */
            sjme_unit_equalL(test, NULL, objectsTread->values.jobjects[1],
                "Stack value did not get cleared.");
        }
    
    /* Success! */
    return SJME_TEST_RESULT_PASS;
}