TST: add test coverage for maybe_promote
- [x] first step for #23833 
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] fixes a regression that was silently introduced in #23796 due to lack of tests

The errors I found for `core.dtypes.cast.maybe_promote` in #23833 were just scratching the surface. I spent about a week on this, because I had to figure out the right behaviour and how to test it simultaneously. I also spent quite some time in making the xfails as sharp as possible, so that they can almost verbatim be turned into counter-examples.

This surely needs a good review, especially for the datetimelike stuff, where I'm not 100% sure whether the interaction with float/integers should cast to object, but it would be my gut feeling.

In any case, this uncovered *lots* of errors, of which I've added some samples below the fold.

The larger points are the following (after which the tests have been modelled):
* `maybe_promote(dtype, fill_value)` fundamentally asks whether it's necessary to upcast `dtype` to accommodate a scalar `fill_value`.
  * In doing so, it should (IMO) be as conservative as possible, i.e. `int8 -> int16` instead of `int8 -> int64`. (currently broken as `int + int -> float`, always)
* It makes sense to use arrays for `fill_value`,  as a sort of vectorized operation for the scalar case (this is used like that in several places, e.g. `maybe_upcast_putmask`)
  * Continuing from the above point, `maybe_promote` should IMO not simply absorb the dtype of `fill_value` (otherwise, might as well call it `always_promote`...), but should be equally prudent whether actual upcasting is necessary.
  * this would mean inspecting the arrays with something like `infer_dtype` (at least in the case of object-typed `fill_value`)
* currently, the second component of the return value is quite useless and inconsistent. I assume it was originally meant to represent the missing value marker for the resulting dtype, but this is not the case for arrays, and doesn't make sense for `int -> int`, for example.
  * I would suggest to add a `return_na_value`-kwarg to determine whether a missing value marker should be returned at all (many places in the code don't care about the second output, see #23833).
* in #23796, @jreback asked me to remove some try/excepts that looked like they were left-overs from ancient numpy. The CI didn't fail on this *only* because the method was not tested sufficiently. As such I've fixed it directly.
* There's a fixture for `all_numpy_dtypes` that got introduced in #21899, but it's a lie - it doesn't cover (nearly) all numpy dtypes. It's currently only used in one test, and so I've adapted it here to be able to *actually* test all numpy dtypes. Nevertheless, I'll split off this part in a separate PR (xref coming).

Here's an incomplete extract of the many things that are going wrong currently:

<details>

```
>>> import numpy as np
>>> from pandas.core.dtypes.cast import maybe_promote
>>>
>>> # should be int16, not int32
>>> maybe_promote(np.dtype('int8'), np.iinfo('int8').max + 1)
(dtype('int32'), 128)
>>>
>>> # should be object, not raise
>>> maybe_promote(np.dtype(int), np.iinfo('uint64').max + 1)
Traceback (most recent call last):
[...]
OverflowError: Python int too large to convert to C long
>>>
>>> # should stay signed, not switch to unsigned
>>> maybe_promote(np.dtype('uint8'), np.iinfo('uint8').max + 1)
(dtype('int32'), 256)
>>>
>>> # should cast to int16, not int32
>>> maybe_promote(np.dtype('uint8'), np.iinfo('int8').min - 1)
(dtype('int32'), -129)
>>> 
>>> # should stay int
>>> maybe_promote(np.dtype('int64'), np.array([1]))
(<class 'numpy.float64'>, nan)
>>>
>>> # should upcast to object, not float
>>> maybe_promote(np.dtype('int64'), np.array([np.iinfo('int64').max + 1]))
(<class 'numpy.float64'>, nan)
>>>
>>> # should only upcast to float32
>>> maybe_promote(np.dtype('int8'), np.array([1], dtype='float32'))
(<class 'numpy.float64'>, nan)
>>>
>>> # should upcast to float64
>>> maybe_promote(np.dtype('float32'), np.finfo('float32').max * 1.1)
(dtype('float32'), 3.7431058130238175e+38)
>>>
>>> # should only upcast to complex64, not complex128
>>> maybe_promote(np.dtype('float32'), 1 + 1j)
(<class 'numpy.complex128'>, (1+1j))
>>>
>>> # should not upcast
>>> maybe_promote(np.dtype('bool'), np.array([True]))
(<class 'numpy.object_'>, nan)
>>>
>>> # should still return nan, not iNaT
>>> maybe_promote(np.dtype('bool'), np.array([1], dtype=np.dtype('datetime64[ns]')))
(<class 'numpy.object_'>, -9223372036854775808)
>>>
>>> # inconsistently transforms fill_value
>>> maybe_promote(np.dtype('datetime64[ns]'), True)
(<class 'numpy.object_'>, nan)
>>>
>>> # should upcast to object
>>> maybe_promote(np.dtype('datetime64[ns]'), np.array([True]))
(dtype('<M8[ns]'), -9223372036854775808)
>>>
>>> # should upcast to object
>>> maybe_promote(np.dtype('bytes'), np.array([True]))
(dtype('S'), nan)
>>>
>>> # inconsistently transforms fill_value
>>> maybe_promote(np.dtype('datetime64[ns]'), np.datetime64(1, 'ns'))
(dtype('<M8[ns]'), 1)
>>>
>>> # should (IMO) cast to object (cf below)
>>> maybe_promote(np.dtype('datetime64[ns]'), 1e10)
(dtype('<M8[ns]'), 10000000000)
>>>
>>> # inconsistently transforms fill_value
>>> maybe_promote(np.dtype('datetime64[ns]'), 1e20)
(<class 'numpy.object_'>, nan)
>>>
>>> # does not upcast correctly (but implicitly turns string to int)
>>> maybe_promote(np.dtype('timedelta64[ns]'), '1')
(dtype('<m8[ns]'), 1)
>>>
>>> # should upcast to object
>>> maybe_promote(np.dtype('int64'), np.timedelta64(1, 'ns'))
(dtype('<m8[ns]'), numpy.timedelta64(1,'ns'))
>>>
>>> # should upcast to object
>>> maybe_promote(np.dtype('float64'), np.timedelta64(1, 'ns'))
(dtype('float64'), numpy.timedelta64(1,'ns'))
>>>
>>> # should upcast to object
>>> maybe_promote(np.dtype('int64'), np.array([1], dtype=str))
(<class 'numpy.float64'>, nan)
>>>
>>> # should upcast to object
>>> maybe_promote(np.dtype(bytes), np.nan)
(dtype('S'), nan)
>>>
>>> # falsely mangles None into NaN
>>> maybe_promote(np.dtype(object), None)
(<class 'numpy.object_'>, nan)
```

</details>