near / near-workspaces-js

Write tests once, run them both on NEAR TestNet and a controlled NEAR Sandbox local environment
https://near.github.io/near-workspaces-js/
GNU General Public License v3.0
42 stars 22 forks source link

Fix issue 220/sandboxserver port conflict #221

Closed fospring closed 9 months ago

fospring commented 9 months ago

https://github.com/near/near-workspaces-js/issues/220

Resolve sanbox server port conflict by adding file lock to sync all SandboxWorker.init() methods, so one available port won't be used twice between testcases. Before add global file lock, two sanboxServer may detecte one port as available in calling SandboxWorker.defaultConfig() before they runing their local nodes with the port as a params in calling SandboxServer.start() in:

  static async init(config: Partial<Config>): Promise<SandboxWorker> {
    debug('Lifecycle.SandboxWorker.create()', 'config:', config);
    const defaultConfig = await this.defaultConfig();
    const worker = new SandboxWorker({...defaultConfig, ...config});
    worker.server = await SandboxServer.init(worker.config);
    await worker.server.start();
    await worker.manager.init();
    return worker;
  }
  static async defaultConfig(): Promise<Config> {
    const port = await SandboxServer.nextPort();
    return {
      ...this.clientConfig,
      homeDir: SandboxServer.randomHomeDir(),
      port,
      rm: false,
      refDir: null,
      rpcAddr: `http://localhost:${port}`,
    };
  }
  async start(): Promise<SandboxServer> {
    debug('Lifecycle.SandboxServer.start()');
    const args = [
      '--home',
      this.homeDir,
      'run',
      '--rpc-addr',
      `0.0.0.0:${this.port}`,
      '--network-addr',
      `0.0.0.0:${await SandboxServer.nextPort()}`,
    ];
    if (process.env.NEAR_WORKSPACES_DEBUG) {
      const filePath = join(this.homeDir, 'sandboxServer.log');
      debug(`near-sandbox logs writing to file: ${filePath}`);
      const fd = await open(filePath, 'a');
      this.subprocess = spawn(SandboxServer.binPath, args, {
        env: {RUST_BACKTRACE: 'full'},
        // @ts-expect-error FileHandle not assignable to Stream | IOType
        stdio: ['ignore', 'ignore', fd],
      });
      this.subprocess.on('exit', async () => {
        await fd.close();
      });
    } else {
      this.subprocess = spawn(SandboxServer.binPath, args, {
        stdio: ['ignore', 'ignore', 'ignore'],
      });
    }

    this.subprocess.on('exit', () => {
      if (!this.readyToDie) {
        debug(`Server with port ${this.port}: died horribly`);
      }
    });
    await sandboxStarted(this.port);
    return this;
  }

Now, I add the file lock to

  async start(): Promise<SandboxServer> {
    debug('Lifecycle.SandboxServer.start()');
    const args = [
      '--home',
      this.homeDir,
      'run',
      '--rpc-addr',
      `0.0.0.0:${this.port}`,
      '--network-addr',
      `0.0.0.0:${await SandboxServer.nextPort()}`,
    ];
    if (process.env.NEAR_WORKSPACES_DEBUG) {
      const filePath = join(this.homeDir, 'sandboxServer.log');
      debug(`near-sandbox logs writing to file: ${filePath}`);
      const fd = await open(filePath, 'a');
      this.subprocess = spawn(SandboxServer.binPath, args, {
        env: {RUST_BACKTRACE: 'full'},
        // @ts-expect-error FileHandle not assignable to Stream | IOType
        stdio: ['ignore', 'ignore', fd],
      });
      this.subprocess.on('exit', async () => {
        await fd.close();
      });
    } else {
      this.subprocess = spawn(SandboxServer.binPath, args, {
        stdio: ['ignore', 'ignore', 'ignore'],
      });
    }

    this.subprocess.on('exit', () => {
      if (!this.readyToDie) {
        debug(`Server with port ${this.port}: died horribly`);
      }
    });
    await sandboxStarted(this.port);
    return this;
  }

So, I added a file lock to preotect instruction from const defaultConfig = await this.defaultConfig() to await worker.server.start() to avoid available ports conflict between testfiles