That's because they added new tests to catch these cases. I recall seeing someone mention in a comment here that coreutils didn't have a test for this either.
So it is reassuring that these things actually get documented and tested.
If I understood correctly, the test suite they are using are the tests used for the original coreutils. Apparently, the behavior that led to this bug wasn't covered by the original test suite (and it looks like the original coreutils just got the behavior "right" without it being tested), so the uutils guys added new tests to cover this after the bug was found.
That makes sense. However, I am generally biased against making significant changes in software (especially rewrites) without also beefing up the test suite, though.
How could they beef it up if they don't know the problem is there?
You can write frivolous tests all you want; bugs are a part of life. They'll occur even if you test thoroughly, especially in new software. It's the response that matters.
They could use the description of what the software is supposed to do and an understanding of the current code to figure out how it should work and what edge cases need to be tested. They can also test random inputs and so on. When you write new software, you do this. When you add features, you do this. When you do a rewrite or refactor, you should also do this.
Is it?
I hope I won't step on somebody's else toes:
GenAI would greatly help cover existing functionality and prevent regressions in new implementation.
For each tool, generate multiple cases, some based on documentation and some from the LLM understanding of the util. Classic input + expected pairs.
Run with both GNU old impl and the new Rust impl.
First - cases where expected+old+new are identical, should go to regression suite.
Now a HUMAN should take a look in this order:
1. Cases where expected+old are identical, but rust is different.
2. If time allows - Cases where expected+rust are identical , but old is different.
TBH, after #1 (expected+old, vs. rust) I'd be asking the GenAI to generate more test cases in these faulty areas.
"You have to catch everything" is much easier said than done, but "add at least one new test"? Nominally the people doing the rewrite should understand what they are rewriting.
Usually, the standard a rewrite is held to is "no worse than the original," which is a very high bar.
I think it's a bit naive to believe that the original coreutils developers only used what is now in the public test suite. Over that length of development, a lot of people probably tested a lot of things even if those tests didn't make it into the official CI suite. If you're doing a rewrite, just writing to the existing tests is really not enough.
It's not "naive". That is the nature of Open Source software. Everything is in the open.
Especially because people will not use a pre-compiled binary, but compile the software themselves (e.g., Gentoo users). So there must be no 'secret' tests, to guarantee that whoever compiles the software, as long as the dependencies are met, will produce a binary with the exact same behavior.
In fact, as an Open Source software, the test suite of the original coreutils is part of the Source package. It's in their (that is, coreutils' maintainers) interest to have the software tested against known edge cases. Because one day their project will be picked up by "some lone developer in Iowa" who will add new features. If there are 'secret' test cases, then the new developer's additions might break things.
This incident is merely coreutils happening to produce correct results on some edge case for uutils.
"Secret" tests have existed forever and will continue to exist. That's the nature of software. What gets pushed is only what the developer wants to maintain, not everything they did in the process of constructing and maintaining that software.
In practice "some lone developer in Iowa" will be held to the standard of quality of the original project if they want to add to it or replace it despite the support they get from the public package. Open-source software is also often not open to being pushed by any random person.
While you develop a feature, you do a lot of tests including white-box tests with the debugger, that would be annoying to automate and maybe wouldn't even survive the next commit. You also do "tests" by manually executing the code in your head modelling all the execution states. Automated tests often only test single cases, while rationing across all states, is hard to automate. They likely also had a specification next to their editor, referencing all the nitpicks in there and proving in their head that these are indeed addressed.
These kind of "tests" often are enforced as the codebase evolved, by having old guys e.g. named Torvalds that yell at new guys. They are hard to formalize short of writing a proof.
This is going to be a problem, considering (more and more unfortunate) Ubuntu's popularity. Scripts will continue to be written against Ubuntu, and if they do not work on your Debian or whatever, it's your problem.
Same thing that happens with Alpine's shell, or macOS, or the BSDs — I work with shell all the time and often run into scripts that should work on non-bash shells, with non-GNU coreutils, but don't, because nobody cared to test them anywhere besides Ubuntu, which until now at least had the same environment as most other Linux distributions.
More pain incoming, for no technical reason at all. Canonical used to feel like a force for the good, but doesn't anymore.
I try to execute my scripts with dash, but I am not sure if that is enough. Do you have a good way recommendation to check, that you're not relying on non-portable behaviour short of installing another distro, that doesn't take much effort.
Working on macOS for years taught me that most people are going to write code that supports what they can test even if there's a better way that works across more systems. Heck, even using 'sed -i' breaks if you go from macOS to Linux or vice-versa but if you don't have a Mac you wouldn't know.
Meanwhile, this is a rewrite of `date` (and other coreutils) with the goal of being perfectly compatible with GNU coreutils (even using the coreutils test cases), which means that differences between the two are going to reduce, not expand.
What you're complaining about here is "people only test on one platform" and your solution is that everything should stay the same and never change, and we should have homogeneity across all platforms forever so that you don't have to deal with a shell script that doesn't work. The actual solution is for more people to become aware of these differences and why best practices exist.
Note that recently Ubuntu and Debian switched /bin/sh to dash instead of bash, which then resulted in a lot of people having to fix their /bin/sh scripts to remove bashisms which then improves things for everyone across all platforms. Now Ubuntu switches to uutils and we find they have a bug in `date` because GNU coreutils didn't have a test for that either; now coreutils has a test for it too so that they don't have a regression in the future, and everyone's software gets better.
There are additionally versioning standards for shared objects, so you can have two incompatible versions of a library live side-by-side on a system, and binaries can link to the one they're compatible with.
> Cargo always picks the newest version of a dependency, even if that version is incompatible with the version of Rust you have installed.
> PKG_CHECK_MODULES([libfoo], [libfoo >= 1.2.3])
This also picks the newest version that might be incompatible with your compiler, if the newer version uses a newer language standard.
> You're like "build this please", and it's like "hey I helpfully upgraded this module! oh and you can't build this at all, your compiler is too old granddad"
Also possible in the case of your example.
> What a bunch of shiny-shiny chasing idiots with a brittle build system.
Autoconf as an example of non-brittle build system? Laughable at best.
This is whataboutism to deflect from Rust's basic ethos being to pull in the latest shiny-shiny.
> This also picks the newest version that might be incompatible with your compiler, if the newer version uses a newer language standard.
It doesn't, it just verifies what the user has already installed (with apt/yum/dnf) is suitable. It certainly doesn't connect to the network and go looking for trouble.
The onus is on library authors to write standard-agnostic, compiler-agnostic headers, and that's what they do:
> This is whataboutism to deflect from Rust's basic ethos being to pull in the latest shiny-shiny.
No. You set a bar for Cargo that the solution you picked does not reach either.
> It doesn't, it just verifies what the user has already installed (with apt/yum/dnf) is suitable.
There's no guarantee that that is compatible with your project though. You might be extra unlucky and have to bring in your own copy of an older version. Plus their dependencies.
Perfect example of the pile of flaming garbage that is C dependency "management". We haven't even mentioned cross-compiling! It multiplies all this C pain a hundredfold.
> The onus is on library authors to write standard-agnostic, compiler-agnostic headers, and that's what they do:
You're assuming that the used feature can be represented in older language standards. If it doesn't, you're forced to at least have that newer compiler on your system.
> [...] standard-agnostic, compiler-agnostic headers [...]
> For linking, shared objects have their [...]
Compiler-agnostic headers that get compiled to compiler-specific calling conventions. If I recall correctly, GCC basically dictates it on Linux. Anyways, I digress.
> shared objects have their own versioning to allow backwards-incompatible versions to exist simultaneously (libfoo.so.1, libfoo.so.2).
Oooh, that one is fun. Now you have to hope that nothing was altered when that old version got built for that new distro. No feature flag changed, no glibc-introduced functional change.
> hey I helpfully upgraded this module! oh and you can't build this at all, your compiler is too old granddad
If we look at your initial example again, Cargo followed your project's build instructions exactly and unfortunately pulled in a package that is for some reason incompatible with your current compiler version. To fix this you have the ability to just specify an older version of the crate and carry on.
Looking at your C example, well, I described what you might have to do and how much manual effort that can be. Being forced to use a newer compiler can be very tedious. Be it due to bugs, stricter standards adherence or just the fact that you have to do it.
In the end, it's not a fair fight comparing dependency management between Rust and C. C loses by all reasonable metrics.
I listed a specific thing -- that Rust's ecosystem grinds people towards newness, even if goes so far to actually break things. It's baked into the design.
I don't care that it's hypothetically possible for that to happen with C, I care that practically, I've never seen it happen.
Whereas, the single piece of software I build that uses Rust, _without changing anything_ (already built before, no source changes, no compiler changes, no system changes) -- cargo install goes off to the fucking internet, finds newer packages, downloads them, and tells me the software it could build last week can't be build any more. What. The. Fuck. Cargo, I didn't ask you to fuck up my shit - but you did it anyway. Make has never done that to me, nor has autoconf.
Show me a C environment that does that, and I'll advise you to throw it out the window and get something better.
There have been about 100 language versions of Rust in the past 10 years. There have been 7 language versions of C in the past 40. They are a world apart, and I far prefer the C world. C programmers see very little reason to adopt "newer" C language editions.
It's like a Python programmer, on a permanent rewrite treadmill because the Python team regularly abandon Python 3.<early version> and introduce Python 3.<new version> with new features that you can't use on earlier Python versions, asking how a Perl programmer copes. The Perl programmer reminds them that the one Perl binary supports and runs every version of Perl from 5.8 onwards, simultaneously, and the idea of making all the developers churn their code over and over again to keep up with latest versions is madness, the most important thing is to make sure old code keeps running without a single change, forever. The two people are simply on different planets.
> I don't care that it's hypothetically possible for that to happen with C, I care that practically, I've never seen it happen.
I don't think your anecdotal experience is enough to redeem the disarray that is C dependency management. It's nice to pretend though.
> and tells me the software it could build last week can't be build any more. What. The. Fuck. Cargo, I didn't ask you to fuck up my shit - but you did it anyway. Make has never done that to me, nor has autoconf.
If you didn't get my point in previous comment, let me put it more frankly - it is your skill issue if you aren't fixing your crates to a specific version but depend on them remaining constant. This is not Cargo's fault.
> Make has never done that to me, nor has autoconf.
Yeah, because they basically guarantee nothing nor allow working around any of the potential issues I've already described.
But you do get to wait for a thousandth time for it to check the size of some types. All those checks are a literal proof how horrible the ecosystem is.
> There have been about 100 language versions of Rust in the past 10 years
There's actually four editions and they're all backwards-compatible.
> C programmers see very little reason to adopt "newer" C language editions.
This doesn't "pick" anything, it only locates and validates the version specified and installed by the user, it does not start to fetch newer versions.
I would use a newer version of C, and consider picking C++, if the choice was between C, C++, Ada, and Rust. (If pattern matching would be a large help, I might consider Rust).
For C++, there is vcpkg and Conan. While they are overall significantly or much worse options than what Rust offers according to many, in large part due to C++'s cruft and backwards compatibility, they do exist.
The way you've described both of those solutions demonstrates perfectly how C package management is an utter mess. You claim to be very familiar with the C ecosystem yet you describe them based on their own description. Not once have you seen them in use? Both of those are also (only) slightly younger than Rust by the way.
So after all these decades there's maybe something vaguely tolerable that's also certainly less mature than what even Rust has. Congrats.
You might be mixing up who you are replying to. I never claimed that C and C++ package management are better than what Rust offers overall. In some regards, Cargo is much better than what is offered in C and C++. I wouldn't ascribe that to a mess, more the difficulty of maintaining backwards compatibility and handling cruft. However, I know of people that have had significant trouble handling Rust. For instance, the inclusion of Rust in bcachefs and handling it in Debian that whole debacle.
They did not have an easy time including Rust software as I read it. Maybe just initial woes, but I have also read other descriptions of distribution maintainers having trouble with integrating Rust software. Dynamic binding complaints? I have not looked into it.
Can detect, but how many are forced? Have you tried using Gentoo with "-Wall -Werror" everywhere?
You have some theoretical guardrails that aren't used widely in practice, many times even can't be used. If they could just be introduced like that, they'd likely be added to the standard in the first place.
The fact that the previous commenter can even ask the question if someone has analyzed or proven coreutils shows how little this "can detect" really guarantees.
The end your "can trivially detect" is very useless compared to Rust's enforcing these guarantees for everyone, all the time.
And rustc uses LLVM, and has had several bugs as well, whether related to LLVM or just due to itself. But what I linked was intentional breakage, and it caused some people a lot of pain.
Yea, I can think of a lot of intentional GCC breakages as well. Especially ones related to optimizations. If we wrote an article for every one you'd never hear the end of it.
Did they change the language? GCC is not meant to change the C or C++ languages (unless the user uses some flag to modify the language), there is an ISO standard that they seek to be compliant with. rustc, on the other hand, only somewhat recently got a specification or something from Ferrocene, and that specification looks lackluster and incomplete from when I last skimmed through it. And rustc does not seem to be developed against the official Rust specification.
That's not what you asked though, these were intentional breakages. Language standard or not.
In any case though, bringing up language specification as an example for maturity is such a massive cop-out considering the amount of UB in C and C++. It's not like it gives you good stability or consistency.
> there is an ISO standard that they seek to be compliant with
You can buy RM 8048 from NIST, is that the "culture" of stability you have in mind?
If breakage is not due to a language change, and the program is fully compliant with the standard, and there is no issue in the standard, then the compiler has a bug and must fix that bug.
If breakage is due to a language change, then even if a program is fully compliant with the previous language version, and the programmer did nothing wrong, then the program is still the one that has a bug. In many language communities, language changes are therefore handled with care and changing the language version is generally set up to be a deliberate action, at least if there would be breakage in backwards compatibility.
I do not see how it would be possible for you not to know that I am completely right about this and that you are completely wrong. For there is absolutely no doubt that that is the case.
> In any case though, bringing up language specification as an example for maturity is such a massive cop-out considering the amount of UB in C and C++.
> If breakage is not due to a language change, and the program is fully compliant with the standard, and there is no issue in the standard, then the compiler has a bug and must fix that bug.
There are almost no C programs without UB. So a lot of what you would call "compiler bugs" are entirely permitted standard. If you say "no true C program has UB" then of course, congrats, your argument might be in some aspects correct. But that's not really the case in practice and your language standard provides shit in terms of practical stability and cross-compatibility in compilers.
> I do not see how it would be possible for you not to know that I am completely right about this and that you are completely wrong. For there is absolutely no doubt that that is the case.
> There are almost no C programs without UB. So a lot of what you would call "compiler bugs" are entirely permitted standard. If you say "no true C program has UB" then of course, congrats, your argument might be in some aspects correct. But that's not really the case in practice and your language standard provides almost no practical stability nor good cross-compatibility in compilers.
If the compiler optimization is compliant with the standard, then it is not a compiler bug. rustc developers have the same expectation when Rust developers mess up using unsafe, though the rules might be less defined for Rust than for C and C++, worsening the issue for Rust.
I don't know where you got the idea that "almost no C programs [are] without UB". Did you get it from personal experience working with C and you having trouble avoiding UB? Unless you have a clear and reliable statistical source or other good source or argument for your claim, I encourage you to rescind that claim. C++ should in some cases be easier to avoid UB with than C.
> I don't know where you got the idea that "almost no C programs [are] without UB". Did you get it from personal experience working with C and you having trouble avoiding UB? Unless you have a clear and reliable statistical source or other good source or argument for your claim, I encourage you to rescind that claim. C++ should in some cases be easier to avoid UB with than C.
From the fact that a lot of compilers can and do rely on UB to do certain optimizations. If UB wasn't widespread, they wouldn't have those optimization passes. You not knowing how widespread UB is in C and C++ codebases is very telling.
You're however absolutely free to find me one large project that will not trigger "-fsanitizer=undefined" for starters. (Generated codebases do not count though.)
> From the fact that a lot of compilers can and do rely on UB to do certain optimizations.
Your understanding of both C, C++ and Rust appear severely flawed. As I already wrote, rustc also uses these kinds of optimizations. And the optimizations do not rely on UB being present in a program, but on UB being absent in a program, and it is the programmer's responsibility to ensure the absence of UB, also in Rust.
Do you truly believe that rustc does not rely on the absence of UB to do optimizations?
Are you a student? Have you graduated anything yet?
> You're however absolutely free to find me one large project that will not trigger "-fsanitizer=undefined" for starters. (Generated codebases do not count though.)
You made the claim, the burden of proof is on you. Though your understanding appear severely flawed, and you need to fix that understanding first.
> Difficult to understand or being unsafe does not make unsafe Rust worse than C. It's an absurd claim.
Even the Rust community at large agrees that unsafe is more difficult than C and C++. So you are completely wrong, and I completely right, yet again.
> Relying on absence of UB is not the same as relying on existence of UB. I'm not surprised however that you find this difference difficult to grasp.
You are coming with complete inanity, instead of fixing your understanding of a subject that you obviously do not have a good understanding of and that I have a much better understanding of than you, as you are well aware of.
You are making both yourself and your fellow Rust proponents look extremely bad. Please do not burden others with your own lack of competence and knowledge. Especially in a submission that is about a bug caused by Rust software.
Man, if I had a nickel every time some old Linux utility ignored a command-line flag I'd have a lot of nickels. I'd have even more nickels if I got one each time some utility parsed command-line flags wrong.
I have automated a lot of things executing other utilities as a subprocess and it's absolutely crazy how many utilities handle CLI flags just seemingly correct, but not really.
I enjoy the ability to do massive refactors and once it builds it works and does the expected. There are so few odd things happening, no unexpected runtime errors.
I've written probably tens of thousands of lines each in languages like C, C++, Python, Java and a few others. None other has been as misery-free. I admit I haven't written Haskell, but it still doesn't very approachable to me.
I can flash a microcontroller with new firmware and it won't magically start spewing out garbage on random occasions because the compiler omitted a nullptr check or that there's an off-by-one error in some odd place. None. Of. That. Shit.
Replacing a program which implements these features and is a core foundation of the OS, which one that doesn't, to mock people not using the latest language is.
Rewriting already perfectly working and feature-complete software and advertising your version as a superior replacement while neglecting to implement existing features that users relied on is pretty stupid, yes.
It's the only thing that has reasonable coverage to effectively block a phishing attack or malware distribution. It can certainly do other things like collecting browsing data, but it does get rid of long-lasting persistent garbage hosted at some bulletproof hosts.
reply