/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

Components.utils.import("resource://gre/modules/ctypes.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");

/**
 * A test to check that .getPosition/.setPosition work with large files.
 * (see bug 952997)
 */

// Test setPosition/getPosition.
function test_setPosition(forward, current, backward) {
  let path = OS.Path.join(OS.Constants.Path.tmpDir,
                          "test_osfile_async_largefiles.tmp");

  // Clear any left-over files from previous runs.
  try {
    yield OS.File.remove(path);
  } catch (ex if ex.becauseNoSuchFile) {
    // ignore
  }

  try {
    let file = yield OS.File.open(path, {write:true, append:false});
    try {
      let pos = 0;

      // 1. seek forward from start
      do_print("Moving forward: " + forward);
      yield file.setPosition(forward, OS.File.POS_START);
      pos += forward;
      do_check_eq((yield file.getPosition()), pos);

      // 2. seek forward from current position
      do_print("Moving current: " + current);
      yield file.setPosition(current, OS.File.POS_CURRENT);
      pos += current;
      do_check_eq((yield file.getPosition()), pos);

      // 3. seek backward from current position
      do_print("Moving current backward: " + backward);
      yield file.setPosition(-backward, OS.File.POS_CURRENT);
      pos -= backward;
      do_check_eq((yield file.getPosition()), pos);

    } finally {
      yield file.setPosition(0, OS.File.POS_START);
      yield file.close();
    }
  } catch(ex) {
    try {
      yield OS.File.remove(path);
    } catch (ex if ex.becauseNoSuchFile) {
      // ignore.
    }
    do_throw(ex);
  }
}

// Test setPosition/getPosition expected failures.
function test_setPosition_failures() {
  let path = OS.Path.join(OS.Constants.Path.tmpDir,
                          "test_osfile_async_largefiles.tmp");

  // Clear any left-over files from previous runs.
  try {
    yield OS.File.remove(path);
  } catch (ex if ex.becauseNoSuchFile) {
    // ignore
  }

  try {
    let file = yield OS.File.open(path, {write:true, append:false});
    try {
      let pos = 0;

      // 1. Use an invalid position value
      try {
        yield file.setPosition(0.5, OS.File.POS_START);
        do_throw("Shouldn't have succeeded");
      } catch (ex) {
        do_check_true(ex.toString().includes("can't pass"));
      }
      // Since setPosition should have bailed, it shouldn't have moved the
      // file pointer at all.
      do_check_eq((yield file.getPosition()), 0);

      // 2. Use an invalid position value
      try {
        yield file.setPosition(0xffffffff + 0.5, OS.File.POS_START);
        do_throw("Shouldn't have succeeded");
      } catch (ex) {
        do_check_true(ex.toString().includes("can't pass"));
      }
      // Since setPosition should have bailed, it shouldn't have moved the
      // file pointer at all.
      do_check_eq((yield file.getPosition()), 0);

      // 3. Use a position that cannot be represented as a double
      try {
        // Not all numbers after 9007199254740992 can be represented as a
        // double. E.g. in js 9007199254740992 + 1 == 9007199254740992
        yield file.setPosition(9007199254740992, OS.File.POS_START);
        yield file.setPosition(1, OS.File.POS_CURRENT);
        do_throw("Shouldn't have succeeded");
      } catch (ex) {
        do_print(ex.toString());
        do_check_true(!!ex);
      }

    } finally {
      yield file.setPosition(0, OS.File.POS_START);
      yield file.close();
      try {
        yield OS.File.remove(path);
      } catch (ex if ex.becauseNoSuchFile) {
        // ignore.
      }
    }
  } catch(ex) {
    do_throw(ex);
  }
}

function run_test() {
  // First verify stuff works for small values.
  add_task(test_setPosition.bind(null, 0, 100, 50));
  add_task(test_setPosition.bind(null, 1000, 100, 50));
  add_task(test_setPosition.bind(null, 1000, -100, -50));

  if (OS.Constants.Win || ctypes.off_t.size >= 8) {
    // Now verify stuff still works for large values.
    // 1. Multiple small seeks, which add up to > MAXINT32
    add_task(test_setPosition.bind(null, 0x7fffffff, 0x7fffffff, 0));
    // 2. Plain large seek, that should end up at 0 again.
    // 0xffffffff also happens to be the INVALID_SET_FILE_POINTER value on
    // Windows, so this also tests the error handling
    add_task(test_setPosition.bind(null, 0, 0xffffffff, 0xffffffff));
    // 3. Multiple large seeks that should end up > MAXINT32.
    add_task(test_setPosition.bind(null, 0xffffffff, 0xffffffff, 0xffffffff));
    // 5. Multiple large seeks with negative offsets.
    add_task(test_setPosition.bind(null, 0xffffffff, -0x7fffffff, 0x7fffffff));

    // 6. Check failures
    add_task(test_setPosition_failures);
  }

  run_next_test();
}
