porsager / postgres

Postgres.js - The Fastest full featured PostgreSQL client for Node.js, Deno, Bun and CloudFlare
The Unlicense
7.51k stars 275 forks source link

postgresjs in combination with deno FakeTime hangs #573

Closed ChristianSiegert closed 1 year ago

ChristianSiegert commented 1 year ago

The Deno standard library provides class FakeTime to freeze/manipulate time so writing tests that involve time is easier. When using postgresjs in combination with FakeTime, tests fail with:

error: Promise resolution is still pending but the event loop has already resolved.

Reproduction:

// test.ts
import { assertEquals } from "https://deno.land/std@0.181.0/testing/asserts.ts";
import { FakeTime } from "https://deno.land/std@0.181.0/testing/time.ts";
import { default as postgres } from "https://deno.land/x/postgresjs@v3.3.4/mod.js";

function createSql() {
  return postgres({
    database: "postgres",
    hostname: "postgres",
    password: "test",
    port: 5432,
    user: "postgres",
  });
}

// Test passes
Deno.test("without FakeTime", async () => {
  const sql = createSql();
  try {
    const result = await sql<{ x: number }[]>`SELECT 1 AS x`;
    assertEquals(result.at(0)?.x, 1);
  } finally {
    await sql.end();
  }
});

// Test hangs
Deno.test("with FakeTime", async () => {
  const sql = createSql();
  const fakeTime = new FakeTime();
  try {
    const result = await sql<{ x: number }[]>`SELECT 1 AS x`;
    assertEquals(result.at(0)?.x, 1);
  } finally {
    fakeTime.restore();
    await sql.end();
  }
});

Run the test file above with deno test --allow-env --allow-net --no-check --unstable test.ts.

Output:

running 2 tests from ./test.ts
without FakeTime ... ok (106ms)
with FakeTime ...
ok | 1 passed | 0 failed (298ms)

error: Promise resolution is still pending but the event loop has already resolved.

Environment:

deno 1.32.1 (release, aarch64-unknown-linux-gnu)
v8 11.2.214.9
typescript 5.0.2
porsager commented 1 year ago

Cool utility 😊 But what is the purpose of the test?

ChristianSiegert commented 1 year ago

I use it to assert that functions set the expected date, e.g.

assertEquals(
  await createUser(sql, { name: "John" }),
  { id: 1, name: "John", dateCreated: new Date(fakeTime.now)},
);

If real time is used, i.e. if the time is not controlled or frozen, reliably specifying the expected dateCreated is impossible.

I found a solution in the meantime. Advance ticks frequently (advanceFrequency) but do not change the fake time in a meaningful way (advanceRate) (deno docs):

const fakeTime = new FakeTime(Date.now(), {
  advanceFrequency: 0.01, // Frequency in milliseconds at which fake time is updated
  advanceRate: 0.01, // Rate relative to real time at which fake time is updated
});

This way postgresjs receives ticks to do its processing but the time in my tests appears frozen, so comparing dates at millisecond resolution works even though the fake time was ticked forward automatically.