Repository: denoland/deno
File: cli/tools/test/mod.rs (Rust)

Code before:
  }

  pub fn overview(&self) -> String {
    match self {
      TestFailure::JsError(js_error) => js_error.exception_message.clone(),
      TestFailure::FailedSteps(1) => "1 test step failed".to_string(),
      TestFailure::FailedSteps(n) => format!("{n} test steps failed"),
      TestFailure::IncompleteSteps => {
        "Completed while steps were still running".to_string()
      }
      TestFailure::Incomplete => "Didn't complete before parent".to_string(),
      TestFailure::Leaked(_, _) => "Leaks detected".to_string(),
      TestFailure::OverlapsWithSanitizers(_) => {
        "Started test step while another test step with sanitizers was running"
          .to_string()
      }
      TestFailure::HasSanitizersAndOverlaps(_) => {
        "Started test step with sanitizers while another test step was running"
          .to_string()
      }
    }
  }

  pub fn error_location(&self) -> Option<TestLocation> {
    match self {
      // TODO: What happens if not enough call stack frames were recorded?
      // TODO: Could anything else trigger the ext:cli/40_test.js file? Maybe we should also test for the method name
      TestFailure::JsError(js_error) => js_error
        .frames
        .iter()
        .position(|v| v.file_name.as_deref() == Some("ext:cli/40_test.js"))
        // Go one up in the stack frame, this is where the user code was
        .and_then(|index| index.checked_sub(1))
        .and_then(|index| {
          let user_frame = &js_error.frames[index];
          let file_name = user_frame.file_name.as_ref()?.to_string();
          // Turn into zero based indices
          let line_number = user_frame.line_number.map(|v| v - 1)? as u32;
          let column_number =
            user_frame.column_number.map(|v| v - 1).unwrap_or(0) as u32;
          Some(TestLocation {
            file_name,
            line_number,
            column_number,
          })
        }),
      _ => None,
    }
  }

  fn format_label(&self) -> String {

Review comment (suggestion):
Turns out that this is a very real issue. If we have about 10 extra call stack frames above the test, then we no longer have enough info to figure out where in the test the error happened.

e.g
```ts
Deno.test("database operations", (t) => {
  throwDeep(10)
});

function throwDeep (d = 0) {
  if(d <= 0) {
  throw new Error("bar");
  } else {
    throwDeep(d - 1)
  }
}
```

Code after:
  }

  pub fn overview(&self) -> String {
    match self {
      TestFailure::JsError(js_error) => js_error.exception_message.clone(),
      TestFailure::FailedSteps(1) => "1 test step failed".to_string(),
      TestFailure::FailedSteps(n) => format!("{n} test steps failed"),
      TestFailure::IncompleteSteps => {
        "Completed while steps were still running".to_string()
      }
      TestFailure::Incomplete => "Didn't complete before parent".to_string(),
      TestFailure::Leaked(_, _) => "Leaks detected".to_string(),
      TestFailure::OverlapsWithSanitizers(_) => {
        "Started test step while another test step with sanitizers was running"
          .to_string()
      }
      TestFailure::HasSanitizersAndOverlaps(_) => {
        "Started test step with sanitizers while another test step was running"
          .to_string()
      }
    }
  }

  pub fn error_location(&self) -> Option<TestLocation> {
    const TEST_RUNNER: &str = "ext:cli/40_test.js";
    match self {
      TestFailure::JsError(js_error) => js_error
        .frames
        .iter()
        // The first line of user code comes above the test file.
        // The call stack usually contains the top 10 frames, and cuts off after that.
        // We need to explicitly check for the test runner here.
        // - Checking for a `ext:` is not enough, since other Deno `ext:`s can appear in the call stack.
        // - This check guarantees that the next frame is inside of the Deno.test(),
        //   and not somewhere else.
        .position(|v| v.file_name.as_deref() == Some(TEST_RUNNER))
        // Go one up in the stack frame, this is where the user code was
        .and_then(|index| index.checked_sub(1))
        .and_then(|index| {
          let user_frame = &js_error.frames[index];
          let file_name = user_frame.file_name.as_ref()?.to_string();
          // Turn into zero based indices
          let line_number = user_frame.line_number.map(|v| v - 1)? as u32;
          let column_number =
            user_frame.column_number.map(|v| v - 1).unwrap_or(0) as u32;
          Some(TestLocation {
            file_name,
            line_number,
            column_number,
          })
        }),