Skip to content

Conversation

osa1
Copy link
Member

@osa1 osa1 commented Sep 11, 2025

Currently canUseSpecialChars returns true when not in Windows and not in tests of the package itself.

This causes color codes to be printed when stdout is redirected or piped.

Fix it by checking if stdout is a tty. If it is, then we assume that most terminals today support colors and return true.

(We can't use the standard library supportsAnsiEscapes as it only returns true when $TERM is xterm, see
dart-lang/sdk#31606.)

Also fix the extended reporter's color support flag initialization, which was hard-coded as true.

Fixes #19.

Currently `canUseSpecialChars` returns `true` when not in Windows and not in tests of the package itself. This causes color codes to be printed when stdout is redirected or piped. Fix it by checking if stdout is a tty. If it is, then we assume that most terminals today support colors and return `true`. (We can't use the standard library `supportsAnsiEscapes` as it only returns `true` when `$TERM` is `xterm`, see dart-lang/sdk#31606.) Also fix the extended reporter's color support flag initialization, which was hard-coded as `true`. Fixes dart-lang#19.
@osa1 osa1 requested a review from a team as a code owner September 11, 2025 10:58
Copy link

github-actions bot commented Sep 11, 2025

PR Health

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

/// https://github.com/dart-lang/sdk/issues/31606
bool get canUseSpecialChars =>
(!Platform.isWindows || stdout.supportsAnsiEscapes) && !inTestTests;
!Platform.isWindows &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it will cause a regression on windows by disabling it always.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the windows implementation of this is not checking the $TERM variable, that only happens on mac and linux.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update this check as before on Windows.

Mac has the same issue as Linux as it compares the TERM with a 4 known TERM values that no one uses. (in TermIsKnownToSupportAnsi)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix should just be to delete this line entirely afaik

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah based on the discussion in the SDK issue and the linked stack overflow thread I'm inclined to change this to just the stdioType(stdout) == StdioType.terminal check and our testing workaround. I think we can drop the windows specific behavior. I don't think we need to wait for a decision SDK changes, we can make the update here now.

I'm also comfortable with the risk of making this change now. I think it's unlikely to break any workflows, and if it does I think it's OK to require a --no-color or --color flag to override.

@jakemac53
Copy link
Contributor

In general, if we believe this is a better way of checking for ansii escape code support, then this should be the std library implementation? I am pretty hesitant to have special behavior here.

What happens if you just remove the !Platform.isWindows part instead?

@osa1
Copy link
Member Author

osa1 commented Sep 11, 2025

What happens if you just remove the !Platform.isWindows part instead?

The problem is not with the Windows check. There are two problems:

  1. Initialization of the color flag is incorrect in ExpandedReporter code. It's hard-coded as true.

  2. supportsAnsiEscapes will never return true on most modern terminals: https://github.com/dart-lang/sdk/blob/2c0648dd7d54413284ab35932bacc572d297cc02/runtime/bin/stdio_linux.cc#L105-L114

    For example, I'm using alacritty, which is not covered by this implementation.

I can't fix just (1) because then it starts using the wrong code in (2) and I never get any colors. So this fixes both in one PR.

I can't separate the two if that's helpful, but we would need to merge (2) first, then (1), to avoid having a state in between that's even worse than the current state.

In general, if we believe this is a better way of checking for ansii escape code support, then this should be the std library implementation? I am pretty hesitant to have special behavior here.

See github.com/dart-lang/sdk/issues/31606. We could implement this properly in the standard library but it won't be trivial.

Instead the applications can assume that all ttys support colors, like we do here.

FWIW I did the same in the SDK's test runner a while ago. We use the same "is tty" check there to enable colors.

As I say in the issue, checking color support is not trivial (need to find and parse terminfo), and I doubt there are terminals in use today that don't support colors. Even if the standard library doesn't want to assume all ttys support colors I think we can safely assume it in the applications.

@jakemac53
Copy link
Contributor

jakemac53 commented Sep 11, 2025

The problem is not with the Windows check. There are two problems:

That is half the problem, the current implementation doesn't even attempt to check for support on non-windows. I am in favor of removing that, fwiw. Your current change makes things worse on windows though by always disabling ansii escape codes on windows, if I read it correctly.

  • Initialization of the color flag is incorrect in ExpandedReporter code. It's hard-coded as true.

Sure this seems like it should be fixed.

supportsAnsiEscapes will never return true on most modern terminals: https://github.com/dart-lang/sdk/blob/2c0648dd7d54413284ab35932bacc572d297cc02/runtime/bin/stdio_linux.cc#L105-L114

If the better implementation here is just to check if we are attached to any terminal, we should change the SDK implementation to that. We don't have to do a perfect implementation before making it better than what it is today.

Otherwise, the argument here is basically that nobody should use that function at all, and so why does it exist? Should it be deprecated?

@osa1
Copy link
Member Author

osa1 commented Sep 11, 2025

Otherwise, the argument here is basically that nobody should use that function at all, and so why does it exist? Should it be deprecated?

Yes, I said the same thing in the link. It's been broken since at least 2017.

import 'package:test_api/src/backend/declarer.dart'; // ignore: implementation_imports
import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports

import 'runner/configuration.dart';
Copy link
Member

@natebosch natebosch Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't import this here, this library must be cross platform and configuration.dart imports dart:io.

We will need a configurable import somewhere if we want to avoid hardcoding this setting - and we'll need to still hardcode it for platforms without dart:io. I think adding a new library just to handle this indirection is OK.

Copy link
Member Author

@osa1 osa1 Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't import this here, this library must be cross platform and configuration.dart imports dart:io.

Just importing dart:io doesn't cause any issues, and we have only one use site of it in Configuration.load, which we don't use in scaffolding.

Is this still an issue? I think it could also make sense to just move Configuration.load to somewhere else and remove dart:io import from runner/configuration.dart.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just importing dart:io doesn't cause any issues, and we have only one use site of it in Configuration.load, which we don't use in scaffolding.

Is this still an issue?

Yes, this is still an issue. We cannot remove the restriction that scaffolding.dart compiles on every platform, so it may not transitively import dart:io without platform specific imports.

I think it could also make sense to just move Configuration.load to somewhere else and remove dart:io import from runner/configuration.dart.

I don't see how that helps. The specific behavior you are importing this file for uses dart:io. That behavior that you are using must be implemented behind a platform specific import. There is no way around this since you specifically need dart:io behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import restrictions are more clearly guarded in bazel, but they also require specific edits in the BUILD file that match exactly the platform specific imports in the libraries. You should be able to confirm the broken behavior in either google3 where the build rules will warn you, or with dart test -p chrome, however the latter doesn't warn early it waits for a slow timeout. We might need to revisit the compilation restrictions for the browser platform to improve the UX.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is still an issue. We cannot remove the restriction that scaffolding.dart compiles on every platform, so it may not transitively import dart:io without platform specific imports.

I don't understand the specific issue, could you elaborate?

You can import dart:io everywhere, you just can't use it (you get a runtime exception). Do you mean we can accidentally use it if we import it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to revisit the compilation restrictions for the browser platform to improve the UX

So it's a bazel/g3 issue? Because I can import dart:io with dart compile js and dart compile wasm just fine locally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can import dart:io everywhere, you just can't use it (you get a runtime exception).

This is not universally true.

So it's a bazel/g3 issue? Because I can import dart:io with dart compile js and dart compile wasm just fine locally.

It will break compatibility with DDC (and therefore build_test is where we'd see it outside of google3). It will also break compatibility with internal dart2js compiles because we are more strict internally than in the dart compile js frontend.

Copy link
Member

@natebosch natebosch Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the ahead of time failures during compiles are considered the much more developer friendly UX. The dart compile js lax behavior was influenced by historical choices. If we get the bandwidth to get around to it we'd like for dart test to also be strict about imports to dart:io on the web platform because it's more developer friendly than runtime exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the ahead of time failures during compiles are considered the much more developer friendly UX

The problem is when you have a third-party transitive import which uses dart:io (or one of the other libs that are not available everywhere) in a limited way. You have to refactor a bunch of packages that you don't own to be able to compile your app.

But maybe that's not an issue for package:test.

@natebosch
Copy link
Member

While we're here we should also add support for checking a NO_COLOR environment variable.

dart-lang/sdk#31606 (comment)

https://no-color.org/

BTW I think it'll be easier to land if the 2 changes are split up. We'll need to make sure we're compatible with google3 when we introduce new configurable imports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

3 participants