Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: port test-format to typescript #20322

Closed

Conversation

allisonkarlitskaya
Copy link
Member

@allisonkarlitskaya allisonkarlitskaya commented Apr 16, 2024

This was mostly pain-free, except that it tests a very wide range of
possible values to the format functions. Expand the type hints
accordingly.

There was one case that required a bit of a nudge — deepEqual wants both
sides to be the same type, and one of them is very static in nature and
the other quite dynamic — just use as unknown to force it down.

@allisonkarlitskaya allisonkarlitskaya added the no-test For doc/workflow changes, or experiments which don't need a full CI run, label Apr 16, 2024
function get_byte_units(guide_value: number, factor?: 1000 | 1024): ByteUnit[];
function format_number(n: number | null | undefined, precision?: number): string
function format_bytes(n: number | null | undefined, factor?: 1000 | 1024 | string, options?: FormatOptions & { separate?: false } | false): string;
function format_bytes(n: number | null | undefined, factor: 1000 | 1024 | string | undefined, options: FormatOptions & { separate: true } | true): string[];
Copy link
Member

Choose a reason for hiding this comment

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

I want to veto this, this is a terrible API. We should not type how we don't want code to be written.

Passing factor as MB and then reverse looking up that it was 1000 is awful imo and we should rid ourselves of 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.

I can accept this argument, but it leaves us in an interesting place: we test this behaviour, but want to make it statically impossible...

Copy link
Member Author

Choose a reason for hiding this comment

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

One way we can do this is with as any casts in the tests... not sure how I feel about it...

Copy link
Member

Choose a reason for hiding this comment

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

My believe is that typing should help us uncover weird things, like the superuser: true and we should then fix this

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's chat about this tomorrow. There's definitely a conversation to be had here...

Copy link
Member Author

Choose a reason for hiding this comment

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

Just to be clear, "get rid of" could mean:

  • completely remove this functionality from cockpit.js, breaking the API
  • remove the advertisement of this functionality from cockpit.d.ts, breaking only typescript users
  • /** @deprecate */ the functionality in cockpit.d.ts
  • something else?

Copy link
Member

Choose a reason for hiding this comment

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

I'd like to actually break the API, given that there are zero known users; but I'm too scared about the unknown ones. So, my gut feeling:

  • Design the API as we want it (see my proposal above, I'm happy to discuss of course)
  • Do the types exactly for this, so that we learn about unintended API usage via type checks
  • Then add some backwards compat hacks inside of format_bytes() to opportunistically interpret a non-object second argument: if it's "1024", treat it as { binary: true }, and ignore all others. console.warn() about it in any case.

Is that possible/reasonable?

Copy link
Member Author

Choose a reason for hiding this comment

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

So here's one proposal that I think you might like, which would let us keep something like the existing podman case:

    /* === Number formatting ===================== */

    interface FormatFactor { _tag: "format-factor" }
    type FormatOptions = {
        output?: "value" | "unit";
        precision?: number;
        base2?: boolean;
        factor?: FormatFactor;
    };

    function format_number(n?: number?, precision?: number): string
    function choose_format_factor(n?: number): FormatFactor;
    function format_bytes(n?: number?, options?: FormatOptions): string;
    function format_bytes_per_sec(n?: number?, options?: FormatOptions): string;
    function format_bits_per_sec(n?: number?, options?: FormatOptions): string;

and here's a simplified version that makes the podman case impossible:

    /* === Number formatting ===================== */

    type FormatOptions = {
        precision?: number;
        base2?: boolean;
    };

    function format_number(n?: number?, precision?: number): string
    function format_bytes(n?: number?, options?: FormatOptions): string;
    function format_bytes_per_sec(n?: number?, options?: FormatOptions): string;
    function format_bits_per_sec(n?: number?, options?: FormatOptions): string;

I'd be OK with either.

Copy link
Member

Choose a reason for hiding this comment

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

TBH I don't understand how the first proposal would work for "the podman case", by which I suppose you mean "uses the separate option" -- it will return a string[] then (or even before cockpit-project/cockpit-podman#1672 it would get a boolean third argument).

Also, for the second one, options is really an object ({ precision: 3 }), whereas in the first one the {} feel more like the grouping for the various types that the second argument could have? (bool or int or string). This doesn't seem consistent?

(That said, I really dislike the current API; bool | int | string is just crazy)

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea with the more complicated API is that you can use choose_format_factor(size) to get an opaque FormatFactor object which can then be passed as factor to multiple invocations of format_bytes() to force the same units from each invocation. You could then opt out of the display of the units with output: "value";

It was just an idea. I think I'd actually prefer not to do it.

@allisonkarlitskaya
Copy link
Member Author

I've split out the hopefully-less-controversial parts of this to separate PRs.

This was mostly pain-free, except that it tests a very wide range of
possible values to the format functions.  Expand the type hints
accordingly.

There was one case that required a bit of a nudge — deepEqual wants both
sides to be the same type, and one of them is very static in nature and
the other quite dynamic — just use `as unknown` to force it down.
@allisonkarlitskaya allisonkarlitskaya changed the title test: add typescript support for qunit test: port test-format to typescript Apr 16, 2024
function format_bytes_per_sec(n: number, factor: 1000 | 1024 | string | undefined, options: FormatOptions & { separate: true } | true): string[];
function format_bits_per_sec(n: number, factor?: 1000 | 1024 | string | undefined, options?: FormatOptions & { separate?: false } | false): string;
function format_bits_per_sec(n: number, factor: 1000 | 1024 | string | undefined, options: FormatOptions & { separate: true } | true): string[];
function get_byte_units(guide_value: number, factor?: 1000 | 1024): [ByteUnit, ByteUnit, ByteUnit];
Copy link
Member

Choose a reason for hiding this comment

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

FTR: There are zero uses of this outside of cockpit.git, and it's not documented. The only place that uses it is in cockpit/main/pkg/storaged/dialog.jsx . Let's make that private?

@allisonkarlitskaya
Copy link
Member Author

#20334

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-test For doc/workflow changes, or experiments which don't need a full CI run,
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants