Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7df646d960 | ||
|
|
74fe558c17 | ||
|
|
7918d64700 | ||
|
|
81d6007410 | ||
|
|
be9fd499e6 | ||
|
|
a5958e31a1 | ||
|
|
29067a0a7d | ||
|
|
ce8f0ac7d0 | ||
|
|
2d71e7144b | ||
|
|
0c1395c215 | ||
|
|
1b3f9f8d5c | ||
|
|
dbfdedf9b3 | ||
|
|
6f96693b35 | ||
|
|
4e5a8d48ba | ||
|
|
acb5832c69 | ||
|
|
f026cb3756 | ||
|
|
73d58ec01b | ||
|
|
e9a6c9e3b3 | ||
|
|
237f3bc566 | ||
|
|
1a7545655c | ||
|
|
22fa810200 | ||
|
|
c3e0908c52 | ||
|
|
3d0273afb3 | ||
|
|
2b9d8b720e | ||
|
|
4687556f8f | ||
|
|
a8824bab09 | ||
|
|
476b4fdf6e | ||
|
|
2229ed7924 | ||
|
|
f9c053d60c | ||
|
|
1108a6c3d2 | ||
|
|
32c929b339 | ||
|
|
00e7245715 | ||
|
|
f596cf1629 | ||
|
|
12927b8840 | ||
|
|
130ed60f4d | ||
|
|
3eb94a482e | ||
|
|
6eca976752 | ||
|
|
e977f023a0 | ||
|
|
dc471cd876 | ||
|
|
6c31a6c8bb | ||
|
|
7535c0c403 | ||
|
|
9fd9bceeaa | ||
|
|
2ca0f1a99c | ||
|
|
6a1c5a449f | ||
|
|
c31d1b3eb0 | ||
|
|
6730d669fb | ||
|
|
fb4ccfa656 | ||
|
|
ff2ea69710 | ||
|
|
2146bb449f | ||
|
|
5a583c753b | ||
|
|
ec31cc2331 | ||
|
|
6549d209d9 | ||
|
|
71d27919ad | ||
|
|
a8aea1c763 | ||
|
|
a72f18b38b | ||
|
|
a1a37c67b6 | ||
|
|
ff73830103 | ||
|
|
950bcc0704 | ||
|
|
80564fc954 | ||
|
|
94e0e386ce | ||
|
|
bfa799b31f | ||
|
|
abe2af477b | ||
|
|
2a82c35e39 | ||
|
|
1f14211a5e | ||
|
|
1ae36fa079 | ||
|
|
e3fc947b92 |
@@ -0,0 +1,87 @@
|
||||
name: builds.hex.pm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
ELIXIR_OPTS: "--warnings-as-errors"
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
concurrency: builds_txt
|
||||
|
||||
jobs:
|
||||
release_pre_built:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- otp: 25
|
||||
otp_version: '25.3'
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- uses: ./.github/workflows/release_pre_built
|
||||
with:
|
||||
otp_version: ${{ matrix.otp_version }}
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
- name: Utils.sh
|
||||
run: |
|
||||
cat << 'EOF' > utils.sh
|
||||
function purge_key() {
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Fastly-Key: ${FASTLY_KEY}" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Length: 0" \
|
||||
"https://api.fastly.com/service/$1/purge/$2"
|
||||
}
|
||||
function purge() {
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
}
|
||||
EOF
|
||||
chmod +x utils.sh
|
||||
- name: Upload Docs to S3
|
||||
if: ${{ matrix.build_docs }}
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
run: |
|
||||
source utils.sh
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
for f in doc/*; do
|
||||
if [ -d "$f" ]; then
|
||||
app=`echo $f | sed s/"doc\/"//`
|
||||
tarball="${app}-${version}.tar.gz"
|
||||
surrogate_key="docs/${app}-${version}"
|
||||
tar -czf "${tarball}" -C "doc/${app}" .
|
||||
aws s3 cp "${tarball}" "s3://${{ env.AWS_S3_BUCKET }}/docs/${tarball}" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
purge "${surrogate_key}"
|
||||
fi
|
||||
done
|
||||
@@ -13,7 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
test_linux:
|
||||
name: Linux, ${{ matrix.otp_release }}, Ubuntu 18.04
|
||||
name: Linux, ${{ matrix.otp_release }}, Ubuntu 20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
development: true
|
||||
- otp_release: maint
|
||||
development: true
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install Erlang/OTP
|
||||
run: |
|
||||
cd $RUNNER_TEMP
|
||||
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-18.04/${{ matrix.otp_release }}.tar.gz
|
||||
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-20.04/${{ matrix.otp_release }}.tar.gz
|
||||
mkdir -p otp
|
||||
tar zxf otp.tar.gz -C otp --strip-components=1
|
||||
otp/Install -minimal $(pwd)/otp
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
check_posix_compliant:
|
||||
name: Check POSIX-compliant
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
name: Notify
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: 24.3
|
||||
elixir-version: 1.13.4
|
||||
otp-version: '25.0'
|
||||
elixir-version: '1.14.0'
|
||||
- name: Run Elixir script
|
||||
env:
|
||||
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
create_draft_release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- otp: 25
|
||||
otp_version: 25.0
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: "Release pre built"
|
||||
description: "Builds elixir release, ExDoc and generates docs"
|
||||
inputs:
|
||||
otp:
|
||||
description: "The major OTP version"
|
||||
otp_version:
|
||||
description: "The exact OTP version (major.minor[.patch])"
|
||||
build_docs:
|
||||
description: "If docs have to be built or not"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ inputs.otp_version }}
|
||||
version-type: strict
|
||||
- name: Build Elixir Release
|
||||
shell: bash
|
||||
run: |
|
||||
make Precompiled.zip
|
||||
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
- name: Get latest stable ExDoc version
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
EX_DOC_LATEST_STABLE_VERSION=$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
|
||||
echo "EX_DOC_LATEST_STABLE_VERSION=${EX_DOC_LATEST_STABLE_VERSION}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
if: ${{ inputs.build_docs }}
|
||||
with:
|
||||
repository: elixir-lang/ex_doc
|
||||
ref: v${{ env.EX_DOC_LATEST_STABLE_VERSION }}
|
||||
path: ex_doc
|
||||
- name: Build ex_doc
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
mv ex_doc ../ex_doc
|
||||
cd ../ex_doc
|
||||
../elixir/bin/mix do local.rebar --force + local.hex --force + deps.get + compile
|
||||
cd ../elixir
|
||||
- name: Build Docs
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
make Docs.zip
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
+88
-1
@@ -241,6 +241,93 @@ protocol, giving developers more control over the struct representation.
|
||||
See the updated documentation for `Inspect` for a general rundown on
|
||||
the approaches and options available.
|
||||
|
||||
## v1.14.5 (2023-05-22)
|
||||
|
||||
This release contains fixes for Erlang/OTP 26.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Fix a bug where stdout would block when there was no attached terminal on Windows when running on Erlang/OTP 26
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Properly set SSL configuration for Mix downloads when running on Erlang/OTP 26
|
||||
|
||||
## v1.14.4 (2023-04-03)
|
||||
|
||||
This release adds basic support for Erlang/OTP 26. When migrating
|
||||
to Erlang/OTP 26, keep it mind it changes how maps are stored
|
||||
internally and they will be printed and traversed in a different
|
||||
order (note maps never provided a guarantee of their order).
|
||||
To aid migration, this release adds `:sort_maps` to `inspect`
|
||||
custom options, in case you want to sort them before inspection:
|
||||
|
||||
inspect(map, custom_options: [sort_maps: true])
|
||||
|
||||
### Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Inspect] Add `:sort_maps` to `Inspect.Opts.custom_options`
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Support shell history in Erlang/OTP 26+
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] Optimize application tracer
|
||||
|
||||
### Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Properly handle blocks with comments in all cases in `Code.quoted_to_string_with_comments/2`
|
||||
* [Kernel] Fix `debug_info/4` when returning core_v1
|
||||
* [Kernel] Store complete path on `quote keep: true` to avoid invalid stacktraces
|
||||
* [Kernel] Fix column count when tokenizing escaped interpolations
|
||||
* [Stream] Fix `Stream.zip/1` hanging on empty list
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Don't call formatter on directories
|
||||
|
||||
## v1.14.3 (2023-01-14)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Speed up loading of runtime modules in the parallel compiler
|
||||
* [Range] Optimize range membership implementation
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Return values from running doctests and make their order consistent
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Fix handling of negative years in `Calendar.strftime/2`
|
||||
* [DateTime] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `DateTime.add/3`)
|
||||
* [Exception] Improve blaming of FunctionClauseError with `is_struct/2` guards
|
||||
* [Kernel] Fix invalid variable scoping in `defguard` expansion
|
||||
* [Kernel] Do not warn on captured underscored vars from `defmodule`
|
||||
* [Kernel] Do not crash for missing line info on type warnings
|
||||
* [NaiveDateTime] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `NaiveDateTime.add/3`)
|
||||
* [Macro] Fix `Macro.to_string/1` for large negative integers
|
||||
* [Macro] Properly type and escape expansion of `__ENV__` in macros
|
||||
* [Path] Make sure `Path.wildcard/2` expands `..` symlinks accordingly
|
||||
* [Range] Address corner cases in `Range.disjoint?/2` implementation
|
||||
* [Time] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `Time.add/3`)
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.DocTest] Remove unnecessary literal quotes from error message on reports
|
||||
|
||||
## v1.14.2 (2022-11-11)
|
||||
|
||||
### 1. Enhancements
|
||||
@@ -435,7 +522,7 @@ the approaches and options available.
|
||||
* [Module] Fix loop while unifying type variables
|
||||
* [Protocol] Warn if a protocol has no definitions
|
||||
* [Regex] Show list options when inspecting a Regex manually defined with `Regex.compile/2`
|
||||
* [String] Allow slices to overflow on both starting and ending positions
|
||||
* [String] Allow slices to overflow on both starting and ending positions
|
||||
* [System] Raise non-generic exception on missing env in `System.fetch_env!/1` to mirror map operations
|
||||
|
||||
#### ExUnit
|
||||
|
||||
@@ -181,39 +181,46 @@ clean_residual_files:
|
||||
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
|
||||
COMPILE_DOCS = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
|
||||
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
|
||||
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
|
||||
|
||||
docs_elixir: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (elixir)"
|
||||
$(Q) rm -rf doc/elixir
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel,--config "lib/elixir/docs.exs")
|
||||
$(call DOCS_COMPILE,Elixir,elixir,Kernel,--config "lib/elixir/scripts/elixir_docs.exs")
|
||||
$(call DOCS_CONFIG,elixir)
|
||||
|
||||
docs_eex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (eex)"
|
||||
$(Q) rm -rf doc/eex
|
||||
$(call COMPILE_DOCS,EEx,eex,EEx,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,EEx,eex,EEx,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,eex)
|
||||
|
||||
docs_mix: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (mix)"
|
||||
$(Q) rm -rf doc/mix
|
||||
$(call COMPILE_DOCS,Mix,mix,Mix,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,Mix,mix,Mix,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,mix)
|
||||
|
||||
docs_iex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (iex)"
|
||||
$(Q) rm -rf doc/iex
|
||||
$(call COMPILE_DOCS,IEx,iex,IEx,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,IEx,iex,IEx,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,iex)
|
||||
|
||||
docs_ex_unit: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (ex_unit)"
|
||||
$(Q) rm -rf doc/ex_unit
|
||||
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,ExUnit,ex_unit,ExUnit,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,ex_unit)
|
||||
|
||||
docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (logger)"
|
||||
$(Q) rm -rf doc/logger
|
||||
$(call COMPILE_DOCS,Logger,logger,Logger,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,Logger,logger,Logger,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -228,7 +235,7 @@ Docs.zip: docs
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -f Precompiled.zip
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
|
||||
|
||||
#==> Test tasks
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.14.2
|
||||
ELIXIR_VERSION=1.14.5
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.14.2
|
||||
set ELIXIR_VERSION=1.14.5
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -791,7 +791,14 @@ defmodule Calendar do
|
||||
|
||||
# Year
|
||||
defp format_modifiers("Y" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result = datetime.year |> Integer.to_string() |> pad_leading(width, pad)
|
||||
{sign, year} =
|
||||
if datetime.year < 0 do
|
||||
{?-, -datetime.year}
|
||||
else
|
||||
{[], datetime.year}
|
||||
end
|
||||
|
||||
result = [sign | year |> Integer.to_string() |> pad_leading(width, pad)]
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
end
|
||||
|
||||
|
||||
@@ -1493,6 +1493,13 @@ defmodule DateTime do
|
||||
iex> dt |> DateTime.add(1, :day, FakeTimeZoneDatabase)
|
||||
#DateTime<2019-04-01 02:00:00+02:00 CEST Europe/Copenhagen>
|
||||
|
||||
This operation merges the precision of the naive date time with the given unit:
|
||||
|
||||
iex> result = DateTime.add(~U[2014-10-02 00:29:10Z], 21, :millisecond)
|
||||
~U[2014-10-02 00:29:10.021Z]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@spec add(
|
||||
@@ -1537,6 +1544,7 @@ defmodule DateTime do
|
||||
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
total_offset = System.convert_time_unit(utc_offset + std_offset, :second, unit)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
result =
|
||||
datetime
|
||||
|
||||
@@ -247,6 +247,17 @@ defmodule Calendar.ISO do
|
||||
defguardp is_utc_offset(offset) when is_integer(offset)
|
||||
defguardp is_std_offset(offset) when is_integer(offset)
|
||||
|
||||
@doc """
|
||||
Converts a `t:System.time_unit/0` to precision.
|
||||
|
||||
Integer-based time units always get maximum precision.
|
||||
"""
|
||||
def time_unit_to_precision(:nanosecond), do: 6
|
||||
def time_unit_to_precision(:microsecond), do: 6
|
||||
def time_unit_to_precision(:millisecond), do: 3
|
||||
def time_unit_to_precision(:second), do: 0
|
||||
def time_unit_to_precision(_), do: 6
|
||||
|
||||
@doc """
|
||||
Parses a time `string` in the `:extended` format.
|
||||
|
||||
|
||||
@@ -361,8 +361,7 @@ defmodule NaiveDateTime do
|
||||
`t:System.time_unit/0`. It defaults to `:second`. Negative values
|
||||
will move backwards in time.
|
||||
|
||||
This function always consider the unit to be computed according
|
||||
to the `Calendar.ISO`.
|
||||
This function always considers the unit to be computed according to the `Calendar.ISO`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -379,7 +378,7 @@ defmodule NaiveDateTime do
|
||||
It can also work with subsecond precisions:
|
||||
|
||||
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10], 2_000, :millisecond)
|
||||
~N[2014-10-02 00:29:12]
|
||||
~N[2014-10-02 00:29:12.000]
|
||||
|
||||
As well as days/hours/minutes:
|
||||
|
||||
@@ -390,16 +389,12 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.add(~N[2015-02-28 00:29:10], 60, :minute)
|
||||
~N[2015-02-28 01:29:10]
|
||||
|
||||
This operation keeps the precision of the naive date time:
|
||||
This operation merges the precision of the naive date time with the given unit:
|
||||
|
||||
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10.021], 21, :second)
|
||||
~N[2014-10-02 00:29:31.021]
|
||||
|
||||
And ignores any changes below the precision:
|
||||
|
||||
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
|
||||
{21000, 0}
|
||||
iex> result = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
~N[2014-10-02 00:29:10.021]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
Operations on top of gregorian seconds or the Unix epoch are optimized:
|
||||
|
||||
@@ -440,6 +435,7 @@ defmodule NaiveDateTime do
|
||||
)
|
||||
when is_integer(amount_to_add) do
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
naive_datetime
|
||||
|> to_iso_days()
|
||||
|
||||
@@ -493,6 +493,13 @@ defmodule Time do
|
||||
iex> Time.add(~T[17:10:05], 30, :minute)
|
||||
~T[17:40:05]
|
||||
|
||||
This operation merges the precision of the time with the given unit:
|
||||
|
||||
iex> result = Time.add(~T[00:29:10], 21, :millisecond)
|
||||
~T[00:29:10.021]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
|
||||
@@ -511,6 +518,7 @@ defmodule Time do
|
||||
amount_to_add = System.convert_time_unit(amount_to_add, unit, :microsecond)
|
||||
total = time_to_microseconds(time) + amount_to_add
|
||||
parts = Integer.mod(total, @parts_per_day)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
{hour, minute, second, {microsecond, _}} =
|
||||
calendar.time_from_day_fraction({parts, @parts_per_day})
|
||||
@@ -736,14 +744,14 @@ defmodule Time do
|
||||
hour: hour1,
|
||||
minute: minute1,
|
||||
second: second1,
|
||||
microsecond: {microsecond1, @parts_per_day}
|
||||
microsecond: {microsecond1, _}
|
||||
},
|
||||
%{
|
||||
calendar: Calendar.ISO,
|
||||
hour: hour2,
|
||||
minute: minute2,
|
||||
second: second2,
|
||||
microsecond: {microsecond2, @parts_per_day}
|
||||
microsecond: {microsecond2, _}
|
||||
},
|
||||
unit
|
||||
) do
|
||||
|
||||
@@ -1583,6 +1583,10 @@ defmodule Code.Formatter do
|
||||
insert_underscores(int_part) <> "." <> decimal_part
|
||||
end
|
||||
|
||||
defp insert_underscores("-" <> digits) do
|
||||
"-" <> insert_underscores(digits)
|
||||
end
|
||||
|
||||
defp insert_underscores(digits) do
|
||||
cond do
|
||||
digits =~ "_" ->
|
||||
|
||||
@@ -70,15 +70,15 @@ defmodule Code.Normalizer do
|
||||
# Atoms with interpolations
|
||||
defp do_normalize(
|
||||
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta,
|
||||
[{:<<>>, _, args} = string, :utf8]},
|
||||
[{:<<>>, _, parts} = string, :utf8]},
|
||||
state
|
||||
)
|
||||
when is_list(args) do
|
||||
when is_list(parts) do
|
||||
dot_meta = patch_meta_line(dot_meta, state.parent_meta)
|
||||
call_meta = patch_meta_line(call_meta, dot_meta)
|
||||
|
||||
utf8 =
|
||||
if args == [] or interpolated?(string) do
|
||||
if parts == [] or binary_interpolated?(parts) do
|
||||
# a non-normalized :utf8 atom signals an atom interpolation
|
||||
:utf8
|
||||
else
|
||||
@@ -96,23 +96,27 @@ defmodule Code.Normalizer do
|
||||
end
|
||||
|
||||
# Charlists with interpolations
|
||||
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}, state) do
|
||||
parts =
|
||||
Enum.map(parts, fn
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
|
||||
args = normalize_args(args, state)
|
||||
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]} = quoted, state) do
|
||||
if list_interpolated?(parts) do
|
||||
parts =
|
||||
Enum.map(parts, fn
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
|
||||
args = normalize_args(args, state)
|
||||
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
|
||||
|
||||
part ->
|
||||
if state.escape do
|
||||
maybe_escape_literal(part, state)
|
||||
else
|
||||
part
|
||||
end
|
||||
end)
|
||||
part when is_binary(part) ->
|
||||
if state.escape do
|
||||
maybe_escape_literal(part, state)
|
||||
else
|
||||
part
|
||||
end
|
||||
end)
|
||||
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
else
|
||||
normalize_call(quoted, state)
|
||||
end
|
||||
end
|
||||
|
||||
# Don't normalize the `Access` atom in access syntax
|
||||
@@ -358,7 +362,7 @@ defmodule Code.Normalizer do
|
||||
normalize_kw_blocks(form, meta, args, state)
|
||||
|
||||
allow_keyword?(form, arity) ->
|
||||
args = normalize_args(args, %{state | parent_meta: state.parent_meta})
|
||||
args = normalize_args(args, %{state | parent_meta: meta})
|
||||
{last_arg, leading_args} = List.pop_at(args, -1, [])
|
||||
|
||||
last_args =
|
||||
@@ -380,7 +384,7 @@ defmodule Code.Normalizer do
|
||||
{form, meta, leading_args ++ last_args}
|
||||
|
||||
true ->
|
||||
args = normalize_args(args, %{state | parent_meta: state.parent_meta})
|
||||
args = normalize_args(args, %{state | parent_meta: meta})
|
||||
{form, meta, args}
|
||||
end
|
||||
end
|
||||
@@ -389,11 +393,11 @@ defmodule Code.Normalizer do
|
||||
defp allow_keyword?(:{}, _), do: false
|
||||
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
|
||||
|
||||
defp normalize_bitstring({:<<>>, meta, parts} = quoted, state, escape_interpolation \\ false) do
|
||||
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation \\ false) do
|
||||
meta = patch_meta_line(meta, state.parent_meta)
|
||||
|
||||
parts =
|
||||
if interpolated?(quoted) do
|
||||
if binary_interpolated?(parts) do
|
||||
normalize_interpolation_parts(parts, %{state | parent_meta: meta}, escape_interpolation)
|
||||
else
|
||||
state = %{state | parent_meta: meta}
|
||||
@@ -543,8 +547,7 @@ defmodule Code.Normalizer do
|
||||
term
|
||||
end
|
||||
|
||||
# Check if we have an interpolated string.
|
||||
defp interpolated?({:<<>>, _, [_ | _] = parts}) do
|
||||
defp binary_interpolated?(parts) do
|
||||
Enum.all?(parts, fn
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
@@ -552,8 +555,12 @@ defmodule Code.Normalizer do
|
||||
end)
|
||||
end
|
||||
|
||||
defp interpolated?(_) do
|
||||
false
|
||||
defp list_interpolated?(parts) do
|
||||
Enum.all?(parts, fn
|
||||
{{:., _, [Kernel, :to_string]}, _, [_]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
|
||||
defp patch_meta_line(meta, parent_meta) do
|
||||
|
||||
@@ -2276,13 +2276,13 @@ defmodule Enum do
|
||||
iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn x -> rem(x, 2) == 0 end)
|
||||
{[4, 2, 0], [5, 3, 1]}
|
||||
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v < 0 end)
|
||||
iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v < 0 end)
|
||||
{[b: -2, d: -3], [a: 1, c: 1]}
|
||||
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v > 50 end)
|
||||
iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v > 50 end)
|
||||
{[], [a: 1, b: -2, c: 1, d: -3]}
|
||||
|
||||
iex> Enum.split_with(%{}, fn {_k, v} -> v > 50 end)
|
||||
iex> Enum.split_with([], fn {_k, v} -> v > 50 end)
|
||||
{[], []}
|
||||
|
||||
"""
|
||||
|
||||
@@ -294,13 +294,11 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp undo_is_struct_guard(
|
||||
{:and, meta, [_, %{node: {_, _, [{_, _, [_, {struct, _, _}]} | optional]}}]}
|
||||
) do
|
||||
defp undo_is_struct_guard({:and, meta, [_, %{node: {_, _, [{_, _, [_, arg]} | optional]}}]}) do
|
||||
args =
|
||||
case optional do
|
||||
[] -> [{struct, meta, nil}]
|
||||
[module] -> [{struct, meta, nil}, module]
|
||||
[] -> [arg]
|
||||
[module] -> [arg, module]
|
||||
end
|
||||
|
||||
%{match?: meta[:value], node: {:is_struct, meta, args}}
|
||||
|
||||
@@ -302,7 +302,12 @@ end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
list = Map.to_list(map)
|
||||
list =
|
||||
if Keyword.get(opts.custom_options, :sort_maps) do
|
||||
map |> Map.to_list() |> :lists.sort()
|
||||
else
|
||||
Map.to_list(map)
|
||||
end
|
||||
|
||||
fun =
|
||||
if Inspect.List.keyword?(list) do
|
||||
|
||||
@@ -31,6 +31,11 @@ defmodule Inspect.Opts do
|
||||
options. Useful when implementing the `Inspect` protocol for nested structs
|
||||
to pass the custom options through.
|
||||
|
||||
It supports some pre-defined keys:
|
||||
- `:sort_maps` (since v1.15.0) - if set to `true`, sorts key-value pairs in maps.
|
||||
This can be helpful to make map inspection deterministic for testing,
|
||||
especially since key order is random since OTP 26.
|
||||
|
||||
* `:inspect_fun` (since v1.9.0) - a function to build algebra documents.
|
||||
Defaults to `Inspect.Opts.default_inspect_fun/0`.
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ defmodule IO.ANSI.Docs do
|
||||
@metadata_filter [:deprecated, :guard, :since]
|
||||
|
||||
defp print_each_metadata(metadata, options) do
|
||||
Enum.reduce(metadata, false, fn
|
||||
metadata
|
||||
|> Enum.sort()
|
||||
|> Enum.reduce(false, fn
|
||||
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
|
||||
label = metadata_label(key, options)
|
||||
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
|
||||
|
||||
@@ -4806,7 +4806,7 @@ defmodule Kernel do
|
||||
var_meta =
|
||||
case prune do
|
||||
true -> [generated: true, keep_unused: true]
|
||||
false -> [generated: false]
|
||||
false -> [generated: true]
|
||||
end
|
||||
|
||||
module_vars = :lists.map(&module_var(&1, var_meta), :maps.keys(versioned_vars))
|
||||
|
||||
@@ -271,12 +271,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
for {{:module, module}, _} <- result,
|
||||
do: module
|
||||
|
||||
runtime_modules =
|
||||
for module <- runtime_modules,
|
||||
path = :code.which(module),
|
||||
is_list(path) and path != [],
|
||||
do: {module, path}
|
||||
|
||||
profile_checker(profile, compiled_modules, runtime_modules, fn ->
|
||||
Module.ParallelChecker.verify(checker, runtime_modules)
|
||||
end)
|
||||
|
||||
@@ -331,7 +331,7 @@ defmodule Kernel.Utils do
|
||||
quote do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true -> unquote(literal_quote(unquote_every_ref(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars, env.module)))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -361,7 +361,7 @@ defmodule Kernel.Utils do
|
||||
end
|
||||
|
||||
# Prefaces `guard` with unquoted versions of `refs`.
|
||||
defp unquote_refs_once(guard, refs) do
|
||||
defp unquote_refs_once(guard, refs, module) do
|
||||
{guard, used_refs} =
|
||||
Macro.postwalk(guard, %{}, fn
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
@@ -375,7 +375,7 @@ defmodule Kernel.Utils do
|
||||
|
||||
%{} ->
|
||||
generated = String.to_atom("arg" <> Integer.to_string(map_size(acc) + 1))
|
||||
new_var = Macro.unique_var(generated, Elixir)
|
||||
new_var = Macro.unique_var(generated, module)
|
||||
{new_var, Map.put(acc, pair, {new_var, var})}
|
||||
end
|
||||
|
||||
|
||||
+11
-6
@@ -1763,7 +1763,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec expand_once(t(), Macro.Env.t()) :: t()
|
||||
@spec expand_once(input(), Macro.Env.t()) :: output()
|
||||
def expand_once(ast, env) do
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
@@ -1795,13 +1795,15 @@ defmodule Macro do
|
||||
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
|
||||
do: {:filename.dirname(env.file), true}
|
||||
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
|
||||
do: {{:%{}, [], Map.to_list(env)}, true}
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom) do
|
||||
env = update_in(env.versioned_vars, &maybe_escape_map/1)
|
||||
{maybe_escape_map(env), true}
|
||||
end
|
||||
|
||||
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env)
|
||||
when is_atom(atom) and is_atom(field) do
|
||||
if Map.has_key?(env, field) do
|
||||
{Map.get(env, field), true}
|
||||
{maybe_escape_map(Map.get(env, field)), true}
|
||||
else
|
||||
{original, false}
|
||||
end
|
||||
@@ -1884,6 +1886,9 @@ defmodule Macro do
|
||||
# Anything else is just returned
|
||||
defp do_expand_once(other, _env), do: {other, false}
|
||||
|
||||
defp maybe_escape_map(map) when is_map(map), do: {:%{}, [], Map.to_list(map)}
|
||||
defp maybe_escape_map(other), do: other
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given name and arity is a special form.
|
||||
"""
|
||||
@@ -1995,7 +2000,7 @@ defmodule Macro do
|
||||
when they change.
|
||||
"""
|
||||
@doc since: "1.14.1"
|
||||
@spec expand_literals(t(), Macro.Env.t()) :: t()
|
||||
@spec expand_literals(input(), Macro.Env.t()) :: output()
|
||||
def expand_literals(ast, env) do
|
||||
{ast, :ok} = expand_literals(ast, :ok, fn node, :ok -> {expand(node, env), :ok} end)
|
||||
ast
|
||||
@@ -2075,7 +2080,7 @@ defmodule Macro do
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
it out for more information and examples.
|
||||
"""
|
||||
@spec expand(t(), Macro.Env.t()) :: t()
|
||||
@spec expand(input(), Macro.Env.t()) :: output()
|
||||
def expand(ast, env) do
|
||||
expand_until({ast, true}, env)
|
||||
end
|
||||
|
||||
@@ -62,7 +62,10 @@ defmodule Module.ParallelChecker do
|
||||
if is_map(info) do
|
||||
info
|
||||
else
|
||||
info |> File.read!() |> maybe_module_map(module)
|
||||
case File.read(info) do
|
||||
{:ok, binary} -> maybe_module_map(binary, module)
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
module_map && cache_from_module_map(ets, module_map)
|
||||
@@ -136,13 +139,13 @@ defmodule Module.ParallelChecker do
|
||||
the modules and adds the ExCk chunk to the binaries. Returns the updated
|
||||
list of warnings from the verification.
|
||||
"""
|
||||
@spec verify(pid(), [{module(), binary()}]) :: [warning()]
|
||||
@spec verify(pid(), [{module(), Path.t()}]) :: [warning()]
|
||||
def verify(checker, runtime_files) do
|
||||
for {module, file} <- runtime_files do
|
||||
spawn({self(), checker}, module, file)
|
||||
end
|
||||
|
||||
modules = :gen_server.call(checker, :start)
|
||||
modules = :gen_server.call(checker, :start, :infinity)
|
||||
collect_results(modules, [])
|
||||
end
|
||||
|
||||
@@ -168,7 +171,7 @@ defmodule Module.ParallelChecker do
|
||||
"""
|
||||
def test_cache do
|
||||
{:ok, checker} = start_link()
|
||||
{checker, :gen_server.call(checker, :ets)}
|
||||
{checker, :gen_server.call(checker, :ets, :infinity)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -324,8 +327,7 @@ defmodule Module.ParallelChecker do
|
||||
|
||||
defp cache_from_chunk(ets, module) do
|
||||
with {^module, binary, _filename} <- :code.get_object_code(module),
|
||||
{:ok, ^module} <- binary |> :beam_lib.info() |> Keyword.fetch(:module),
|
||||
{:ok, {_, [{'ExCk', chunk}]}} <- :beam_lib.chunks(binary, ['ExCk']),
|
||||
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
|
||||
{:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do
|
||||
cache_chunk(ets, module, contents.exports)
|
||||
true
|
||||
|
||||
@@ -135,8 +135,7 @@ defmodule Module.Types do
|
||||
# Collect relevant information from context and traces to report error
|
||||
def error_to_warning(:unable_apply, {mfa, args, expected, signature, stack}, context) do
|
||||
{fun, arity} = context.function
|
||||
line = get_meta(stack.last_expr)[:line]
|
||||
location = {context.file, line, {context.module, fun, arity}}
|
||||
location = {context.file, get_line(stack), {context.module, fun, arity}}
|
||||
|
||||
traces = type_traces(stack, context)
|
||||
{[signature | args], traces} = lift_all_types([signature | args], traces, context)
|
||||
@@ -146,8 +145,7 @@ defmodule Module.Types do
|
||||
|
||||
def error_to_warning(:unable_unify, {left, right, stack}, context) do
|
||||
{fun, arity} = context.function
|
||||
line = get_meta(stack.last_expr)[:line]
|
||||
location = {context.file, line, {context.module, fun, arity}}
|
||||
location = {context.file, get_line(stack), {context.module, fun, arity}}
|
||||
|
||||
traces = type_traces(stack, context)
|
||||
{[left, right], traces} = lift_all_types([left, right], traces, context)
|
||||
@@ -155,6 +153,8 @@ defmodule Module.Types do
|
||||
{Module.Types, error, location}
|
||||
end
|
||||
|
||||
defp get_line(stack), do: stack.last_expr |> get_meta() |> Keyword.get(:line, 0)
|
||||
|
||||
# Collect relevant traces from context.traces using stack.unify_stack
|
||||
defp type_traces(stack, context) do
|
||||
# TODO: Do we need the unify_stack or is enough to only get the last variable
|
||||
@@ -252,7 +252,7 @@ defmodule Module.Types do
|
||||
"undefined field \"#{atom}\" ",
|
||||
format_expr(expr, location),
|
||||
"expected one of the following fields: ",
|
||||
Enum.map_join(Enum.sort(known_atoms), ", ", & &1),
|
||||
Enum.join(Enum.sort(known_atoms), ", "),
|
||||
"\n\n",
|
||||
traces,
|
||||
format_message_hints(hints),
|
||||
|
||||
@@ -603,6 +603,10 @@ defmodule Path do
|
||||
:file.read_link_info(file)
|
||||
end
|
||||
|
||||
def read_file_info(file) do
|
||||
:file.read_file_info(file)
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case :file.list_dir(dir) do
|
||||
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
|
||||
+31
-27
@@ -294,22 +294,24 @@ defmodule Range do
|
||||
iex> Range.disjoint?(1..10//2, 2..10//2)
|
||||
true
|
||||
|
||||
# First element in common in all below is 29
|
||||
iex> Range.disjoint?(2..100//3, 9..100//5)
|
||||
false
|
||||
iex> Range.disjoint?(101..2//-3, 99..9//-5)
|
||||
false
|
||||
# First element in common is 29
|
||||
iex> Range.disjoint?(1..100//14, 8..100//21)
|
||||
false
|
||||
iex> Range.disjoint?(57..-1//-14, 8..100//21)
|
||||
false
|
||||
iex> Range.disjoint?(1..100//14, 51..8//-21)
|
||||
iex> Range.disjoint?(1..100//14, 50..8//-21)
|
||||
false
|
||||
|
||||
# If 29 is out of range
|
||||
iex> Range.disjoint?(1..28//14, 8..28//21)
|
||||
true
|
||||
|
||||
# First element in common is 14
|
||||
iex> Range.disjoint?(2..28//3, 9..28//5)
|
||||
false
|
||||
iex> Range.disjoint?(26..2//-3, 29..9//-5)
|
||||
false
|
||||
|
||||
# Starting from the back without alignment
|
||||
iex> Range.disjoint?(27..11//-3, 30..0//-7)
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -334,21 +336,28 @@ defmodule Range do
|
||||
# progressions and see if they belong within the ranges
|
||||
# https://math.stackexchange.com/questions/1656120/formula-to-find-the-first-intersection-of-two-arithmetic-progressions
|
||||
{gcd, u, v} = Integer.extended_gcd(-step1, step2)
|
||||
c = first1 - first2 + step2 - step1
|
||||
t1 = -c / step1 * u
|
||||
t2 = -c / step2 * v
|
||||
t = max(floor(t1) + 1, floor(t2) + 1)
|
||||
x = div(c * u + t * step2, gcd) - 1
|
||||
y = div(c * v + t * step1, gcd) - 1
|
||||
|
||||
x < 0 or first1 + x * step1 > last1 or
|
||||
y < 0 or first2 + y * step2 > last2
|
||||
if rem(first2 - first1, gcd) == 0 do
|
||||
c = first1 - first2 + step2 - step1
|
||||
t1 = -c / step2 * u
|
||||
t2 = -c / step1 * v
|
||||
t = max(floor(t1) + 1, floor(t2) + 1)
|
||||
x = div(c * u + t * step2, gcd) - 1
|
||||
y = div(c * v + t * step1, gcd) - 1
|
||||
|
||||
x < 0 or first1 + x * step1 > last1 or
|
||||
y < 0 or first2 + y * step2 > last2
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@compile inline: [normalize: 3]
|
||||
defp normalize(first, last, step) when first > last, do: {last, first, -step}
|
||||
defp normalize(first, last, step) when first > last,
|
||||
do: {first - abs(div(first - last, step) * step), first, -step}
|
||||
|
||||
defp normalize(first, last, step), do: {first, last, step}
|
||||
|
||||
@doc false
|
||||
@@ -387,16 +396,11 @@ defimpl Enumerable, for: Range do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first..last//step = range, value) when is_integer(value) do
|
||||
cond do
|
||||
Range.size(range) == 0 ->
|
||||
{:ok, false}
|
||||
|
||||
first <= last ->
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
|
||||
true ->
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
def member?(first..last//step, value) when is_integer(value) do
|
||||
if step > 0 do
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
else
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1315,6 +1315,8 @@ defmodule Stream do
|
||||
{:suspended, acc, &zip_list(enumerables, &1, fun, zip_fun)}
|
||||
end
|
||||
|
||||
defp zip_list([], {:cont, acc}, _fun, _zip_fun), do: {:done, acc}
|
||||
|
||||
defp zip_list(enumerables, {:cont, acc}, fun, zip_fun) do
|
||||
case zip_list_heads_tails(enumerables, [], []) do
|
||||
{heads, tails} -> zip_list(tails, fun.(zip_fun.(heads), acc), fun, zip_fun)
|
||||
|
||||
@@ -366,6 +366,7 @@ defmodule StringIO do
|
||||
defp get_until(encoding, prompt, mod, fun, args, %{input: input} = state) do
|
||||
case get_until(input, encoding, mod, fun, args, [], 0) do
|
||||
{result, input, count} ->
|
||||
# Convert :eof to "" as they are both treated the same
|
||||
input =
|
||||
case input do
|
||||
:eof -> ""
|
||||
@@ -392,7 +393,7 @@ defmodule StringIO do
|
||||
defp get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
case bytes_until_eol(chars, encoding, 0) do
|
||||
{kind, size} when kind in [:split, :replace_split] ->
|
||||
{line, rest} = :erlang.split_binary(chars, size)
|
||||
<<line::binary-size(size), rest::binary>> = chars
|
||||
|
||||
case apply(mod, fun, [continuation, binary_to_list(line, encoding) | args]) do
|
||||
{:done, result, :eof} ->
|
||||
|
||||
@@ -82,7 +82,7 @@ defmodule Supervisor do
|
||||
GenServer.call(Counter, :get)
|
||||
#=> 0
|
||||
|
||||
GenServer.cast(Counter, {:bump, 3})
|
||||
GenServer.call(Counter, {:bump, 3})
|
||||
#=> 0
|
||||
|
||||
GenServer.call(Counter, :get)
|
||||
|
||||
@@ -42,7 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
|
||||
|
||||
Elixir version | Supported Erlang/OTP versions
|
||||
:------------- | :-------------------------------
|
||||
1.14 | 23 - 25
|
||||
1.14 | 23 - 25 (and Erlang/OTP 26 from v1.14.5)
|
||||
1.13 | 22 - 24 (and Erlang/OTP 25 from v1.13.4)
|
||||
1.12 | 22 - 24
|
||||
1.11 | 21 - 23 (and Erlang/OTP 24 from v1.11.4)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generate docs_config.js for version chooser in ExDoc
|
||||
[app] = System.argv()
|
||||
|
||||
{text_tags, 0} = System.cmd("git", ["tag"])
|
||||
skipped = Version.parse!("1.0.3")
|
||||
|
||||
list_contents =
|
||||
text_tags
|
||||
|> String.split()
|
||||
|> Enum.map(fn "v" <> rest -> Version.parse!(rest) end)
|
||||
|> Enum.filter(&(Version.compare(&1, skipped) == :gt))
|
||||
|> Enum.sort({:desc, Version})
|
||||
|> Enum.map_intersperse(", ", fn version ->
|
||||
version_string = Version.to_string(version)
|
||||
~s[{"version":"v#{version_string}", "url":"https://hexdocs.pm/#{app}/#{version_string}"}]
|
||||
end)
|
||||
|
||||
File.mkdir_p!("doc/#{app}")
|
||||
File.write!("doc/#{app}/docs_config.js", ["var versionNodes = [", list_contents, "];\n"])
|
||||
@@ -25,9 +25,9 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
_ = parse_otp_release(),
|
||||
OTP = parse_otp_release(),
|
||||
preload_common_modules(),
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(),
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP),
|
||||
check_file_encoding(file:native_name_encoding()),
|
||||
|
||||
case application:get_env(elixir, check_endianness, true) of
|
||||
@@ -87,7 +87,10 @@ stop(Tab) ->
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8() ->
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP) when OTP >= 26 ->
|
||||
ok = io:setopts(standard_io, [binary]),
|
||||
ok;
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(_OTP) ->
|
||||
%% In case there is a shell, we can't really change its
|
||||
%% encoding, so we just set binary to true. Otherwise
|
||||
%% we must set the encoding as the user with no shell
|
||||
|
||||
@@ -24,7 +24,7 @@ debug_info(core_v1, _Module, {elixir_v1, Map, Specs}, Opts) ->
|
||||
%% warnings nor the other functionality provided there.
|
||||
case elixir_erl_compiler:erl_to_core(Prefix ++ Specs ++ Forms, AllOpts) of
|
||||
{ok, CoreForms, _} ->
|
||||
try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, core, return | AllOpts]) of
|
||||
try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, to_core, return | AllOpts]) of
|
||||
{ok, _, Core, _} -> {ok, Core};
|
||||
_What -> {error, failed_conversion}
|
||||
catch
|
||||
@@ -65,7 +65,8 @@ elixir_to_erl([], Ann) ->
|
||||
elixir_to_erl(<<>>, Ann) ->
|
||||
{bin, Ann, []};
|
||||
elixir_to_erl(#{} = Map, Ann) ->
|
||||
Assocs = [{map_field_assoc, Ann, elixir_to_erl(K, Ann), elixir_to_erl(V, Ann)} || {K, V} <- maps:to_list(Map)],
|
||||
Assocs = [{map_field_assoc, Ann, elixir_to_erl(K, Ann), elixir_to_erl(V, Ann)}
|
||||
|| {K, V} <- lists:sort(maps:to_list(Map))],
|
||||
{map, Ann, Assocs};
|
||||
elixir_to_erl(Tree, Ann) when is_list(Tree) ->
|
||||
elixir_to_erl_cons(Tree, Ann);
|
||||
|
||||
@@ -29,7 +29,7 @@ translate_into(Meta, Cases, Expr, Opts, S) ->
|
||||
{TInto, SI} =
|
||||
case lists:keyfind(into, 1, Opts) of
|
||||
{into, Into} -> elixir_erl_pass:translate(Into, Ann, S);
|
||||
false -> {{nil, Ann}, S}
|
||||
false -> {false, S}
|
||||
end,
|
||||
|
||||
TUniq = lists:keyfind(uniq, 1, Opts) == {uniq, true},
|
||||
|
||||
@@ -173,7 +173,7 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
|
||||
|
||||
{File, Line} = case lists:keyfind(location, 1, EOpts) of
|
||||
{location, keep} ->
|
||||
{elixir_utils:relative_to_cwd(?key(E, file)), false};
|
||||
{?key(E, file), false};
|
||||
false ->
|
||||
{proplists:get_value(file, EOpts, nil), proplists:get_value(line, EOpts, false)}
|
||||
end,
|
||||
|
||||
@@ -39,7 +39,7 @@ extract([$\\, Last, Last, Last | Rest], Buffer, Output, Line, Column, Scope, Int
|
||||
extract(Rest, [Last, Last, Last | Buffer], Output, Line, Column+4, Scope, Interpol, All);
|
||||
|
||||
extract([$\\, $#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
|
||||
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+1, Scope, true, Last);
|
||||
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+3, Scope, true, Last);
|
||||
|
||||
extract([$#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
|
||||
Output1 = build_string(Buffer, Output),
|
||||
|
||||
@@ -25,7 +25,7 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, S, #{context := Context} =
|
||||
AssocKeys = [K || {K, _} <- Assocs],
|
||||
Struct = load_struct(Meta, ELeft, [Assocs], AssocKeys, EE),
|
||||
Keys = ['__struct__'] ++ AssocKeys,
|
||||
WithoutKeys = maps:to_list(maps:without(Keys, Struct)),
|
||||
WithoutKeys = lists:sort(maps:to_list(maps:without(Keys, Struct))),
|
||||
StructAssocs = elixir_quote:escape(WithoutKeys, none, false),
|
||||
{{'%', Meta, [ELeft, {'%{}', MapMeta, StructAssocs ++ Assocs}]}, SE, EE};
|
||||
|
||||
|
||||
@@ -364,5 +364,12 @@ defmodule CalendarTest do
|
||||
Calendar.strftime(~D[2019-08-15], "%D", unknown: "option")
|
||||
end
|
||||
end
|
||||
|
||||
test "zero padding for negative year" do
|
||||
assert Calendar.strftime(Date.new!(-1, 1, 1), "%Y") == "-0001"
|
||||
assert Calendar.strftime(Date.new!(-11, 1, 1), "%Y") == "-0011"
|
||||
assert Calendar.strftime(Date.new!(-111, 1, 1), "%Y") == "-0111"
|
||||
assert Calendar.strftime(Date.new!(-1111, 1, 1), "%Y") == "-1111"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -507,6 +507,28 @@ defmodule Code.Normalizer.FormatterASTTest do
|
||||
# after
|
||||
""",
|
||||
literal_encoder: fn literal, _ -> {:ok, literal} end
|
||||
|
||||
assert_same """
|
||||
block do
|
||||
# before 1
|
||||
1 + 1
|
||||
|
||||
# before 2
|
||||
2 + 2
|
||||
end
|
||||
""",
|
||||
literal_encoder: fn literal, _ -> {:ok, literal} end
|
||||
|
||||
assert_same """
|
||||
block do
|
||||
# before 1
|
||||
Mix.install([1 + 1])
|
||||
|
||||
# before 2
|
||||
Mix.install([2 + 2])
|
||||
end
|
||||
""",
|
||||
literal_encoder: fn literal, _ -> {:ok, literal} end
|
||||
end
|
||||
|
||||
test "before and after expressions with newlines" do
|
||||
|
||||
@@ -515,6 +515,17 @@ defmodule Code.Normalizer.QuotedASTTest do
|
||||
test "charlist" do
|
||||
assert quoted_to_string(quote(do: [])) == "[]"
|
||||
assert quoted_to_string(quote(do: 'abc')) == "'abc'"
|
||||
|
||||
# False positive
|
||||
assert quoted_to_string(
|
||||
quote do
|
||||
:"Elixir.List".to_charlist([
|
||||
case var do
|
||||
var -> var
|
||||
end
|
||||
])
|
||||
end
|
||||
) =~ "List.to_charlist([\n case var do\n var -> var\n end\n])"
|
||||
end
|
||||
|
||||
test "string" do
|
||||
|
||||
@@ -735,11 +735,11 @@ defmodule EnumTest do
|
||||
|
||||
assert Enum.split_with(%{}, fn x -> rem(x, 2) == 0 end) == {[], []}
|
||||
|
||||
assert Enum.split_with(%{a: 1, b: 2, c: 3}, fn {_k, v} -> rem(v, 2) == 0 end) ==
|
||||
{[b: 2], [a: 1, c: 3]}
|
||||
assert Enum.split_with(%{a: 1, b: 2}, fn {_k, v} -> rem(v, 2) == 0 end) ==
|
||||
{[b: 2], [a: 1]}
|
||||
|
||||
assert Enum.split_with(%{b: 2, d: 4, f: 6}, fn {_k, v} -> rem(v, 2) == 0 end) ==
|
||||
{[b: 2, d: 4, f: 6], []}
|
||||
{Map.to_list(%{b: 2, d: 4, f: 6}), []}
|
||||
end
|
||||
|
||||
test "random/1" do
|
||||
@@ -1503,14 +1503,11 @@ defmodule EnumTest do
|
||||
assert result == [2, 4, 6, 8, 10]
|
||||
|
||||
# Maps
|
||||
result = Enum.zip_with(%{a: 7, c: 9}, 3..4, fn {key, value}, b -> {key, value + b} end)
|
||||
assert result == [a: 10, c: 13]
|
||||
result = Enum.zip_with(%{a: 7}, 3..4, fn {key, value}, b -> {key, value + b} end)
|
||||
assert result == [a: 10]
|
||||
|
||||
colour_1 = %{r: 176, g: 175, b: 255}
|
||||
colour_2 = %{r: 12, g: 176, b: 176}
|
||||
|
||||
result = Enum.zip_with(colour_1, colour_2, fn {k, left}, {k, right} -> {k, left + right} end)
|
||||
assert result == [b: 431, g: 351, r: 188]
|
||||
result = Enum.zip_with(3..4, %{a: 7}, fn a, {key, value} -> {key, value + a} end)
|
||||
assert result == [a: 10]
|
||||
end
|
||||
|
||||
test "zip_with/2" do
|
||||
@@ -1518,8 +1515,10 @@ defmodule EnumTest do
|
||||
result = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"]], zip_fun)
|
||||
assert result == [{:a, 1, "foo"}, {:b, 2, "bar"}]
|
||||
|
||||
lots = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"], %{a: :b, c: :d}], zip_fun)
|
||||
assert lots == [{:a, 1, "foo", {:a, :b}}, {:b, 2, "bar", {:c, :d}}]
|
||||
map = %{a: :b, c: :d}
|
||||
[x1, x2] = Map.to_list(map)
|
||||
lots = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"], map], zip_fun)
|
||||
assert lots == [{:a, 1, "foo", x1}, {:b, 2, "bar", x2}]
|
||||
|
||||
assert Enum.zip_with([[:a, :b], [1, 2, 3, 4], ["foo", "bar", "baz", "qux"]], zip_fun) ==
|
||||
[{:a, 1, "foo"}, {:b, 2, "bar"}]
|
||||
@@ -1556,21 +1555,11 @@ defmodule EnumTest do
|
||||
assert result == [2, 4, 6, 8, 10]
|
||||
|
||||
# Maps
|
||||
result = Enum.zip_with([%{a: 7, c: 9}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
|
||||
assert result == [a: 10, c: 13]
|
||||
result = Enum.zip_with([%{a: 7}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
|
||||
assert result == [a: 10]
|
||||
|
||||
colour_1 = %{r: 176, g: 175, b: 255}
|
||||
colour_2 = %{r: 12, g: 176, b: 176}
|
||||
|
||||
result =
|
||||
Enum.zip_with([colour_1, colour_2], fn [{k, left}, {k, right}] -> {k, left + right} end)
|
||||
|
||||
assert result == [b: 431, g: 351, r: 188]
|
||||
|
||||
assert Enum.zip_with([%{a: :b, c: :d}, %{e: :f, g: :h}], & &1) == [
|
||||
[a: :b, e: :f],
|
||||
[c: :d, g: :h]
|
||||
]
|
||||
result = Enum.zip_with([%{a: 7}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
|
||||
assert result == [a: 10]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2396,16 +2385,17 @@ defmodule EnumTest.Map do
|
||||
|
||||
test "random/1" do
|
||||
map = %{a: 1, b: 2, c: 3}
|
||||
[x1, x2, x3] = Map.to_list(map)
|
||||
seed1 = {1406, 407_414, 139_258}
|
||||
seed2 = {1406, 421_106, 567_597}
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.random(map) == {:c, 3}
|
||||
assert Enum.random(map) == {:a, 1}
|
||||
assert Enum.random(map) == {:b, 2}
|
||||
assert Enum.random(map) == x3
|
||||
assert Enum.random(map) == x1
|
||||
assert Enum.random(map) == x2
|
||||
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.random(map) == {:c, 3}
|
||||
assert Enum.random(map) == {:b, 2}
|
||||
assert Enum.random(map) == x3
|
||||
assert Enum.random(map) == x2
|
||||
end
|
||||
|
||||
test "take_random/2" do
|
||||
@@ -2418,35 +2408,37 @@ defmodule EnumTest.Map do
|
||||
# set a fixed seed so the test can be deterministic
|
||||
# please note the order of following assertions is important
|
||||
map = %{a: 1, b: 2, c: 3}
|
||||
[x1, x2, x3] = Map.to_list(map)
|
||||
seed1 = {1406, 407_414, 139_258}
|
||||
seed2 = {1406, 421_106, 567_597}
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 1) == [c: 3]
|
||||
assert Enum.take_random(map, 1) == [x3]
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 2) == [c: 3, a: 1]
|
||||
assert Enum.take_random(map, 2) == [x3, x1]
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 3) == [c: 3, a: 1, b: 2]
|
||||
assert Enum.take_random(map, 3) == [x3, x1, x2]
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 4) == [c: 3, a: 1, b: 2]
|
||||
assert Enum.take_random(map, 4) == [x3, x1, x2]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 1) == [a: 1]
|
||||
assert Enum.take_random(map, 1) == [x1]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 2) == [a: 1, c: 3]
|
||||
assert Enum.take_random(map, 2) == [x1, x3]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 3) == [a: 1, c: 3, b: 2]
|
||||
assert Enum.take_random(map, 3) == [x1, x3, x2]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 4) == [a: 1, c: 3, b: 2]
|
||||
assert Enum.take_random(map, 4) == [x1, x3, x2]
|
||||
end
|
||||
|
||||
test "reverse/1" do
|
||||
assert Enum.reverse(%{}) == []
|
||||
assert Enum.reverse(MapSet.new()) == []
|
||||
assert Enum.reverse(%{a: 1, b: 2, c: 3}) == [c: 3, b: 2, a: 1]
|
||||
|
||||
map = %{a: 1, b: 2, c: 3}
|
||||
assert Enum.reverse(map) == Map.to_list(map) |> Enum.reverse()
|
||||
end
|
||||
|
||||
test "reverse/2" do
|
||||
assert Enum.reverse([a: 1, b: 2, c: 3, a: 1], %{x: 1, y: 2, z: 3}) ==
|
||||
[a: 1, c: 3, b: 2, a: 1, x: 1, y: 2, z: 3]
|
||||
assert Enum.reverse([a: 1, b: 2, c: 3, a: 1], %{x: 1}) == [a: 1, c: 3, b: 2, a: 1, x: 1]
|
||||
|
||||
assert Enum.reverse([], %{a: 1}) == [a: 1]
|
||||
assert Enum.reverse([], %{}) == []
|
||||
@@ -2456,44 +2448,49 @@ defmodule EnumTest.Map do
|
||||
|
||||
test "fetch/2" do
|
||||
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
assert Enum.fetch(map, 0) == {:ok, {:a, 1}}
|
||||
assert Enum.fetch(map, -2) == {:ok, {:d, 4}}
|
||||
[x1, _x2, _x3, x4, x5] = Map.to_list(map)
|
||||
assert Enum.fetch(map, 0) == {:ok, x1}
|
||||
assert Enum.fetch(map, -2) == {:ok, x4}
|
||||
assert Enum.fetch(map, -6) == :error
|
||||
assert Enum.fetch(map, 5) == :error
|
||||
assert Enum.fetch(%{}, 0) == :error
|
||||
|
||||
assert Stream.take(map, 3) |> Enum.fetch(3) == :error
|
||||
assert Stream.take(map, 5) |> Enum.fetch(4) == {:ok, {:e, 5}}
|
||||
assert Stream.take(map, 5) |> Enum.fetch(4) == {:ok, x5}
|
||||
end
|
||||
|
||||
test "map_intersperse/3" do
|
||||
assert Enum.map_intersperse(%{}, :a, & &1) == []
|
||||
assert Enum.map_intersperse(%{foo: :bar}, :a, & &1) == [{:foo, :bar}]
|
||||
|
||||
assert Enum.map_intersperse(%{foo: :bar, baz: :bat}, :a, & &1) ==
|
||||
[{:baz, :bat}, :a, {:foo, :bar}]
|
||||
map = %{foo: :bar, baz: :bat}
|
||||
[x1, x2] = Map.to_list(map)
|
||||
|
||||
assert Enum.map_intersperse(map, :a, & &1) == [x1, :a, x2]
|
||||
end
|
||||
|
||||
test "slice/2" do
|
||||
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
assert Enum.slice(map, 0..0) == [a: 1]
|
||||
assert Enum.slice(map, 0..1) == [a: 1, b: 2]
|
||||
assert Enum.slice(map, 0..2) == [a: 1, b: 2, c: 3]
|
||||
[x1, x2, x3 | _] = Map.to_list(map)
|
||||
assert Enum.slice(map, 0..0) == [x1]
|
||||
assert Enum.slice(map, 0..1) == [x1, x2]
|
||||
assert Enum.slice(map, 0..2) == [x1, x2, x3]
|
||||
end
|
||||
|
||||
test "slice/3" do
|
||||
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
assert Enum.slice(map, 1, 2) == [b: 2, c: 3]
|
||||
[x1, x2, x3, x4, x5] = Map.to_list(map)
|
||||
assert Enum.slice(map, 1, 2) == [x2, x3]
|
||||
assert Enum.slice(map, 1, 0) == []
|
||||
assert Enum.slice(map, 2, 5) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 6) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 5) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 2, 6) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 5, 5) == []
|
||||
assert Enum.slice(map, 6, 5) == []
|
||||
assert Enum.slice(map, 6, 0) == []
|
||||
assert Enum.slice(map, -6, 0) == []
|
||||
assert Enum.slice(map, -6, 5) == [a: 1, b: 2, c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, -2, 5) == [d: 4, e: 5]
|
||||
assert Enum.slice(map, -3, 1) == [c: 3]
|
||||
assert Enum.slice(map, -6, 5) == [x1, x2, x3, x4, x5]
|
||||
assert Enum.slice(map, -2, 5) == [x4, x5]
|
||||
assert Enum.slice(map, -3, 1) == [x3]
|
||||
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
Enum.slice(map, 0, -1)
|
||||
@@ -2508,19 +2505,19 @@ defmodule EnumTest.Map do
|
||||
end
|
||||
|
||||
assert Enum.slice(map, 0, 0) == []
|
||||
assert Enum.slice(map, 0, 1) == [a: 1]
|
||||
assert Enum.slice(map, 0, 2) == [a: 1, b: 2]
|
||||
assert Enum.slice(map, 1, 2) == [b: 2, c: 3]
|
||||
assert Enum.slice(map, 0, 1) == [x1]
|
||||
assert Enum.slice(map, 0, 2) == [x1, x2]
|
||||
assert Enum.slice(map, 1, 2) == [x2, x3]
|
||||
assert Enum.slice(map, 1, 0) == []
|
||||
assert Enum.slice(map, 2, 5) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 6) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 5) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 2, 6) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 5, 5) == []
|
||||
assert Enum.slice(map, 6, 5) == []
|
||||
assert Enum.slice(map, 6, 0) == []
|
||||
assert Enum.slice(map, -6, 0) == []
|
||||
assert Enum.slice(map, -6, 5) == [a: 1, b: 2, c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, -2, 5) == [d: 4, e: 5]
|
||||
assert Enum.slice(map, -3, 1) == [c: 3]
|
||||
assert Enum.slice(map, -6, 5) == [x1, x2, x3, x4, x5]
|
||||
assert Enum.slice(map, -2, 5) == [x4, x5]
|
||||
assert Enum.slice(map, -3, 1) == [x3]
|
||||
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
Enum.slice(map, 0, -1)
|
||||
|
||||
@@ -18,13 +18,20 @@ defmodule ExceptionTest do
|
||||
end
|
||||
end
|
||||
|
||||
assert Exception.message(%{__struct__: BadException, __exception__: true, raise: true}) =~
|
||||
"got RuntimeError with message \"oops\" while retrieving Exception.message/1 " <>
|
||||
"for %{__exception__: true, __struct__: ExceptionTest.BadException, raise: true}"
|
||||
assert "got RuntimeError with message \"oops\" while retrieving Exception.message/1 for %{" <>
|
||||
inspected =
|
||||
Exception.message(%{__struct__: BadException, __exception__: true, raise: true})
|
||||
|
||||
assert Exception.message(%{__struct__: BadException, __exception__: true, raise: false}) =~
|
||||
"got nil while retrieving Exception.message/1 " <>
|
||||
"for %{__exception__: true, __struct__: ExceptionTest.BadException, raise: false}"
|
||||
assert inspected =~ "raise: true"
|
||||
assert inspected =~ "__exception__: true"
|
||||
assert inspected =~ "__struct__: ExceptionTest.BadException"
|
||||
|
||||
assert "got nil while retrieving Exception.message/1 for %{" <> inspected =
|
||||
Exception.message(%{__struct__: BadException, __exception__: true, raise: false})
|
||||
|
||||
assert inspected =~ "raise: false"
|
||||
assert inspected =~ "__exception__: true"
|
||||
assert inspected =~ "__struct__: ExceptionTest.BadException"
|
||||
end
|
||||
|
||||
test "normalize/2" do
|
||||
@@ -426,6 +433,8 @@ defmodule ExceptionTest do
|
||||
when is_binary(url) or (is_struct(url) and is_struct(url, url_module) and false) do
|
||||
url
|
||||
end
|
||||
|
||||
def sub_get!(url) when is_struct(url.sub, URI), do: url.sub
|
||||
end
|
||||
)
|
||||
|
||||
@@ -475,6 +484,19 @@ defmodule ExceptionTest do
|
||||
|
||||
def get!(url) when -is_binary(url)- or is_struct(url) and is_struct(url, URI) and -false-
|
||||
"""
|
||||
|
||||
assert blame_message(Req, & &1.sub_get!(%{})) =~ """
|
||||
no function clause matching in ExceptionTest.Req.sub_get!/1
|
||||
|
||||
The following arguments were given to ExceptionTest.Req.sub_get!/1:
|
||||
|
||||
# 1
|
||||
%{}
|
||||
|
||||
Attempted function clauses (showing 1 out of 1):
|
||||
|
||||
def sub_get!(url) when -is_struct(url.sub, URI)-
|
||||
"""
|
||||
end
|
||||
|
||||
test "annotates badarg on apply" do
|
||||
|
||||
@@ -417,14 +417,20 @@ defmodule Inspect.MapTest do
|
||||
test "basic" do
|
||||
assert inspect(%{1 => "b"}) == "%{1 => \"b\"}"
|
||||
|
||||
assert inspect(%{1 => "b", 2 => "c"}, pretty: true, width: 1) ==
|
||||
assert inspect(%{1 => "b", 2 => "c"},
|
||||
pretty: true,
|
||||
width: 1,
|
||||
custom_options: [sort_maps: true]
|
||||
) ==
|
||||
"%{\n 1 => \"b\",\n 2 => \"c\"\n}"
|
||||
end
|
||||
|
||||
test "keyword" do
|
||||
assert inspect(%{a: 1}) == "%{a: 1}"
|
||||
assert inspect(%{a: 1, b: 2}) == "%{a: 1, b: 2}"
|
||||
assert inspect(%{a: 1, b: 2, c: 3}) == "%{a: 1, b: 2, c: 3}"
|
||||
assert inspect(%{a: 1, b: 2}, custom_options: [sort_maps: true]) == "%{a: 1, b: 2}"
|
||||
|
||||
assert inspect(%{a: 1, b: 2, c: 3}, custom_options: [sort_maps: true]) ==
|
||||
"%{a: 1, b: 2, c: 3}"
|
||||
end
|
||||
|
||||
test "with limit" do
|
||||
@@ -446,12 +452,12 @@ defmodule Inspect.MapTest do
|
||||
test "public modified struct" do
|
||||
public = %Public{key: 1}
|
||||
|
||||
assert inspect(Map.put(public, :foo, :bar)) ==
|
||||
assert inspect(Map.put(public, :foo, :bar), custom_options: [sort_maps: true]) ==
|
||||
"%{__struct__: Inspect.MapTest.Public, foo: :bar, key: 1}"
|
||||
end
|
||||
|
||||
test "private struct" do
|
||||
assert inspect(%{__struct__: Private, key: 1}) ==
|
||||
assert inspect(%{__struct__: Private, key: 1}, custom_options: [sort_maps: true]) ==
|
||||
"%{__struct__: Inspect.MapTest.Private, key: 1}"
|
||||
end
|
||||
|
||||
@@ -486,7 +492,7 @@ defmodule Inspect.MapTest do
|
||||
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
|
||||
'''
|
||||
|
||||
assert inspect(%Failing{name: "Foo"}) =~ message
|
||||
assert inspect(%Failing{name: "Foo"}, custom_options: [sort_maps: true]) =~ message
|
||||
end
|
||||
|
||||
test "safely inspect bad implementation disables colors" do
|
||||
@@ -505,7 +511,10 @@ defmodule Inspect.MapTest do
|
||||
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
|
||||
'''
|
||||
|
||||
assert inspect(%Failing{name: "Foo"}, syntax_colors: [atom: [:green]]) =~ message
|
||||
assert inspect(%Failing{name: "Foo"},
|
||||
syntax_colors: [atom: [:green]],
|
||||
custom_options: [sort_maps: true]
|
||||
) =~ message
|
||||
end
|
||||
|
||||
test "unsafely inspect bad implementation" do
|
||||
@@ -524,7 +533,7 @@ defmodule Inspect.MapTest do
|
||||
'''
|
||||
|
||||
try do
|
||||
inspect(%Failing{name: "Foo"}, safe: false)
|
||||
inspect(%Failing{name: "Foo"}, safe: false, custom_options: [sort_maps: true])
|
||||
rescue
|
||||
exception in Inspect.Error ->
|
||||
assert Exception.message(exception) =~ exception_message
|
||||
@@ -552,14 +561,16 @@ defmodule Inspect.MapTest do
|
||||
|
||||
while inspecting:
|
||||
|
||||
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
|
||||
'''
|
||||
|
||||
try do
|
||||
Enum.to_list(%Failing{name: "Foo"})
|
||||
rescue
|
||||
exception in Protocol.UndefinedError ->
|
||||
assert Exception.message(exception) =~ exception_message
|
||||
message = Exception.message(exception)
|
||||
assert message =~ exception_message
|
||||
assert message =~ "__struct__: Inspect.MapTest.Failing"
|
||||
assert message =~ "name: \"Foo\""
|
||||
|
||||
assert [
|
||||
{Enumerable, :impl_for!, 1, _} | _
|
||||
@@ -617,7 +628,7 @@ defmodule Inspect.MapTest do
|
||||
)
|
||||
)
|
||||
|
||||
assert inspect(%Failing{name: "Foo"}) =~ message
|
||||
assert inspect(%Failing{name: "Foo"}, custom_options: [sort_maps: true]) =~ message
|
||||
assert inspected =~ message
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,26 @@ defmodule Kernel.DialyzerTest do
|
||||
end
|
||||
|
||||
# Add a few key Elixir modules for types and macro functions
|
||||
mods = [:elixir, :elixir_env, Atom, Enum, Exception, Kernel, Macro, Macro.Env, String]
|
||||
mods = [
|
||||
:elixir,
|
||||
:elixir_env,
|
||||
ArgumentError,
|
||||
Atom,
|
||||
Enum,
|
||||
Exception,
|
||||
ExUnit.AssertionError,
|
||||
ExUnit.Assertions,
|
||||
IO,
|
||||
Kernel,
|
||||
Kernel.Utils,
|
||||
List,
|
||||
Macro,
|
||||
Macro.Env,
|
||||
Module,
|
||||
String,
|
||||
String.Chars
|
||||
]
|
||||
|
||||
files = Enum.map(mods, &:code.which/1)
|
||||
dialyzer_run(analysis_type: :plt_build, output_plt: plt, apps: [:erts], files: files)
|
||||
|
||||
@@ -102,6 +121,7 @@ defmodule Kernel.DialyzerTest do
|
||||
|
||||
copy_beam!(context, ProtocolOpaque)
|
||||
copy_beam!(context, ProtocolOpaque.Entity)
|
||||
copy_beam!(context, ProtocolOpaque.Entity.Any)
|
||||
copy_beam!(context, ProtocolOpaque.Duck)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Kernel.QuoteTest do
|
||||
line = __ENV__.line + 2
|
||||
|
||||
assert quote(location: :keep, do: bar(1, 2, 3)) ==
|
||||
{:bar, [keep: {Path.relative_to_cwd(__ENV__.file), line}], [1, 2, 3]}
|
||||
{:bar, [keep: {__ENV__.file, line}], [1, 2, 3]}
|
||||
end
|
||||
|
||||
test "fixed line" do
|
||||
|
||||
@@ -411,6 +411,7 @@ defmodule Kernel.RaiseTest do
|
||||
fun = BadFunction.Missing.fun()
|
||||
|
||||
:code.delete(BadFunction.Missing)
|
||||
:code.purge(BadFunction.Missing)
|
||||
|
||||
defmodule BadFunction.Missing do
|
||||
def fun, do: fn -> :another end
|
||||
|
||||
@@ -172,10 +172,18 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test "env" do
|
||||
env = %{__ENV__ | line: 0}
|
||||
assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
|
||||
env = %{__ENV__ | line: 0, lexical_tracker: self()}
|
||||
|
||||
expanded = Macro.expand_once(quote(do: __ENV__), env)
|
||||
assert Macro.validate(expanded) == :ok
|
||||
assert Code.eval_quoted(expanded) == {env, []}
|
||||
|
||||
assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
|
||||
assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
|
||||
|
||||
expanded = Macro.expand_once(quote(do: __ENV__.versioned_vars), env)
|
||||
assert Macro.validate(expanded) == :ok
|
||||
assert Code.eval_quoted(expanded) == {env.versioned_vars, []}
|
||||
end
|
||||
|
||||
defmacro local_macro(), do: raise("ignored")
|
||||
@@ -421,6 +429,16 @@ defmodule MacroTest do
|
||||
test "converts quoted to string" do
|
||||
assert Macro.to_string(quote do: hello(world)) == "hello(world)"
|
||||
end
|
||||
|
||||
test "large number literals" do
|
||||
# with quote
|
||||
assert Macro.to_string(quote do: 576_460_752_303_423_455) == "576_460_752_303_423_455"
|
||||
assert Macro.to_string(quote do: -576_460_752_303_423_455) == "-576_460_752_303_423_455"
|
||||
|
||||
# without quote
|
||||
assert Macro.to_string(576_460_752_303_423_455) == "576_460_752_303_423_455"
|
||||
assert Macro.to_string(-576_460_752_303_423_455) == "-576_460_752_303_423_455"
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_string/2" do
|
||||
|
||||
@@ -206,7 +206,7 @@ defmodule MapTest do
|
||||
assert Map.replace!(map, :b, 10) == %{c: 3, b: 10, a: 1}
|
||||
assert Map.replace!(map, :a, 1) == map
|
||||
|
||||
assert_raise KeyError, "key :x not found in: %{a: 1, b: 2, c: 3}", fn ->
|
||||
assert_raise KeyError, ~r/key :x not found in: %{.*a: 1.*}/, fn ->
|
||||
Map.replace!(map, :x, 10)
|
||||
end
|
||||
|
||||
|
||||
@@ -562,6 +562,22 @@ defmodule Module.Types.IntegrationTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "regressions" do
|
||||
test "handle missing location info from quoted" do
|
||||
assert capture_io(:stderr, fn ->
|
||||
quote do
|
||||
defmodule X do
|
||||
def f() do
|
||||
x = %{}
|
||||
%{x | key: :value}
|
||||
end
|
||||
end
|
||||
end
|
||||
|> Code.compile_quoted()
|
||||
end) =~ "warning:"
|
||||
end
|
||||
end
|
||||
|
||||
describe "after_verify" do
|
||||
test "reports functions" do
|
||||
files = %{
|
||||
|
||||
@@ -140,25 +140,25 @@ defmodule Module.Types.PatternTest do
|
||||
end
|
||||
|
||||
test "struct" do
|
||||
assert quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{}) ==
|
||||
{:ok,
|
||||
{:map,
|
||||
[
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :bar}, :dynamic},
|
||||
{:required, {:atom, :baz}, :dynamic},
|
||||
{:required, {:atom, :foo}, :dynamic}
|
||||
]}}
|
||||
assert {:ok, {:map, fields}} = quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{})
|
||||
|
||||
assert quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{foo: 123, bar: :atom}) ==
|
||||
{:ok,
|
||||
{:map,
|
||||
[
|
||||
{:required, {:atom, :foo}, :integer},
|
||||
{:required, {:atom, :bar}, {:atom, :atom}},
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :baz}, :dynamic}
|
||||
]}}
|
||||
assert Enum.sort(fields) == [
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :bar}, :dynamic},
|
||||
{:required, {:atom, :baz}, :dynamic},
|
||||
{:required, {:atom, :foo}, :dynamic}
|
||||
]
|
||||
|
||||
assert {:ok, {:map, fields}} =
|
||||
quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{foo: 123, bar: :atom})
|
||||
|
||||
assert Enum.sort(fields) ==
|
||||
[
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :bar}, {:atom, :atom}},
|
||||
{:required, {:atom, :baz}, :dynamic},
|
||||
{:required, {:atom, :foo}, :integer}
|
||||
]
|
||||
end
|
||||
|
||||
test "struct var" do
|
||||
|
||||
@@ -332,6 +332,13 @@ defmodule ModuleTest do
|
||||
assert Elixir.ModuleTest.NonAtomAlias.hello() == :world
|
||||
end
|
||||
|
||||
test "does not warn on captured underscored vars" do
|
||||
_unused = 123
|
||||
|
||||
defmodule __MODULE__.NoVarWarning do
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:no_warn_undefined, ModuleCreateSample}
|
||||
|
||||
test "create" do
|
||||
|
||||
@@ -38,6 +38,21 @@ defmodule PathTest do
|
||||
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
test "wildcard/2 follows ..", config do
|
||||
hello = Path.join(config.tmp_dir, "wildcard/hello")
|
||||
world = Path.join(config.tmp_dir, "wildcard/world")
|
||||
File.mkdir_p(hello)
|
||||
File.touch(world)
|
||||
|
||||
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/w*/../h*")) == []
|
||||
|
||||
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/h*/../w*")) ==
|
||||
[Path.join(config.tmp_dir, "wildcard/hello/../world")]
|
||||
after
|
||||
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
|
||||
end
|
||||
|
||||
test "wildcard/2 raises on null byte" do
|
||||
assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end
|
||||
end
|
||||
|
||||
@@ -1340,6 +1340,8 @@ defmodule StreamTest do
|
||||
assert Stream.chunk_every([0, 1, 2, 3], 2) |> Stream.zip() |> Enum.to_list() ==
|
||||
[{0, 2}, {1, 3}]
|
||||
|
||||
assert Stream.zip([]) |> Enum.to_list() == []
|
||||
|
||||
stream = %HaltAcc{acc: 1..3}
|
||||
assert Stream.zip([1..3, stream]) |> Enum.to_list() == [{1, 1}, {2, 2}, {3, 3}]
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ extract_interpolations_without_interpolation_test() ->
|
||||
|
||||
extract_interpolations_with_escaped_interpolation_test() ->
|
||||
["f\\#{o}o"] = extract_interpolations("f\\#{o}o"),
|
||||
{1, 8, ["f\\#{o}o"], [], _} =
|
||||
{1, 10, ["f\\#{o}o"], [], _} =
|
||||
elixir_interpolation:extract(1, 2, #elixir_tokenizer{}, true, "f\\#{o}o\"", $").
|
||||
|
||||
extract_interpolations_with_interpolation_test() ->
|
||||
|
||||
@@ -199,6 +199,11 @@ interpolation_test() ->
|
||||
{concat_op, {1, 10, nil}, '<>'},
|
||||
{bin_string, {1, 13, nil}, [<<>>]}] = tokenize("\"f#{oo}\" <> \"\"").
|
||||
|
||||
escaped_interpolation_test() ->
|
||||
[{bin_string, {1, 1, nil}, [<<"f#{oo}">>]},
|
||||
{concat_op, {1, 11, nil}, '<>'},
|
||||
{bin_string, {1, 14, nil}, [<<>>]}] = tokenize("\"f\\#{oo}\" <> \"\"").
|
||||
|
||||
capture_test() ->
|
||||
% Parens precedence
|
||||
[{capture_op, {1, 1, nil}, '&'},
|
||||
|
||||
@@ -337,7 +337,9 @@ defmodule ExUnit.CLIFormatter do
|
||||
end
|
||||
|
||||
defp format_test_type_counts(%{test_counter: test_counter} = _config) do
|
||||
Enum.map(test_counter, fn {test_type, count} ->
|
||||
test_counter
|
||||
|> Enum.sort()
|
||||
|> Enum.map(fn {test_type, count} ->
|
||||
type_pluralized = pluralize(count, test_type, ExUnit.plural_rule(test_type |> to_string()))
|
||||
"#{count} #{type_pluralized}, "
|
||||
end)
|
||||
|
||||
@@ -238,9 +238,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
extract(module)
|
||||
|> filter_by_opts(module, opts)
|
||||
|> Stream.with_index(1)
|
||||
|> Enum.map(fn {test, acc} ->
|
||||
compile_test(test, module, import, acc, file, tags)
|
||||
|> Enum.sort_by(& &1.line)
|
||||
|> Enum.with_index(fn test, index ->
|
||||
compile_test(test, module, import, index + 1, file, tags)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -313,8 +313,8 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
tests =
|
||||
Enum.map(exprs, fn {expr, expected, formatted} ->
|
||||
test_case_content(expr, expected, location, stack, formatted)
|
||||
Enum.map(exprs, fn {expr, expected, doctest} ->
|
||||
test_case_content(expr, expected, location, stack, doctest)
|
||||
end)
|
||||
|
||||
{:__block__, [], test_import(module, do_import) ++ tests}
|
||||
@@ -327,12 +327,11 @@ defmodule ExUnit.DocTest do
|
||||
end) > 1
|
||||
end
|
||||
|
||||
defp test_case_content(expr, :test, location, stack, formatted) do
|
||||
string_to_quoted(location, stack, expr, "\n" <> formatted) |> insert_assertions()
|
||||
defp test_case_content(expr, :test, location, stack, doctest) do
|
||||
string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:test, expected}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
defp test_case_content(expr, {:test, expected}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
expected_ast = string_to_quoted(location, stack, expected, doctest)
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
@@ -340,56 +339,53 @@ defmodule ExUnit.DocTest do
|
||||
quote do
|
||||
value = unquote(expr_ast)
|
||||
expected = unquote(expected_ast)
|
||||
formatted = unquote(formatted)
|
||||
doctest = unquote(doctest)
|
||||
last_expr = unquote(last_expr)
|
||||
expected_expr = unquote(expected)
|
||||
stack = unquote(stack)
|
||||
|
||||
ExUnit.DocTest.__test__(value, expected, formatted, last_expr, expected_expr, stack)
|
||||
ExUnit.DocTest.__test__(value, expected, doctest, last_expr, expected_expr, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:inspect, expected}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
defp test_case_content(expr, {:inspect, expected}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
expected_ast = string_to_quoted(location, stack, expected, doctest)
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
|
||||
quote do
|
||||
value = unquote(expr_ast)
|
||||
expected = unquote(expected_ast)
|
||||
formatted = unquote(formatted)
|
||||
expected = unquote(expected)
|
||||
doctest = unquote(doctest)
|
||||
last_expr = unquote(last_expr)
|
||||
expected_expr = unquote(expected)
|
||||
expected_expr = unquote(inspect(expected))
|
||||
stack = unquote(stack)
|
||||
|
||||
ExUnit.DocTest.__inspect__(value, expected, formatted, last_expr, expected_expr, stack)
|
||||
ExUnit.DocTest.__inspect__(value, expected, doctest, last_expr, expected_expr, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:error, exception, message}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
defp test_case_content(expr, {:error, exception, message}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest)
|
||||
|
||||
quote do
|
||||
stack = unquote(stack)
|
||||
message = unquote(message)
|
||||
formatted = unquote(formatted)
|
||||
doctest = unquote(doctest)
|
||||
exception = unquote(exception)
|
||||
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, formatted, stack)
|
||||
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, doctest, stack)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __test__(value, expected, formatted, last_expr, expected_expr, stack) do
|
||||
def __test__(value, expected, doctest, last_expr, expected_expr, stack) do
|
||||
case value do
|
||||
^expected ->
|
||||
:ok
|
||||
{:ok, value}
|
||||
|
||||
_ ->
|
||||
error = [
|
||||
message: "Doctest failed",
|
||||
doctest: "\n" <> formatted <> "\n" <> expected_expr,
|
||||
doctest: doctest,
|
||||
expr: "#{last_expr} === #{String.trim(expected_expr)}",
|
||||
left: value,
|
||||
right: expected
|
||||
@@ -400,7 +396,7 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __inspect__(value, expected, formatted, last_expr, expected_expr, parent_stack) do
|
||||
def __inspect__(value, expected, doctest, last_expr, expected_expr, parent_stack) do
|
||||
result =
|
||||
try do
|
||||
inspect(value, safe: false)
|
||||
@@ -415,10 +411,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
case result do
|
||||
:ok ->
|
||||
:ok
|
||||
{:ok, value}
|
||||
|
||||
{extra, stack} ->
|
||||
doctest = "\n" <> formatted <> "\n" <> expected_expr
|
||||
expr = "inspect(#{last_expr}) === #{String.trim(expected_expr)}"
|
||||
error = [doctest: doctest, expr: expr] ++ extra
|
||||
reraise ExUnit.AssertionError, error, stack ++ parent_stack
|
||||
@@ -426,7 +421,7 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __error__(fun, message, exception, formatted, stack) do
|
||||
def __error__(fun, message, exception, doctest, stack) do
|
||||
try do
|
||||
fun.()
|
||||
rescue
|
||||
@@ -451,12 +446,10 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
if failed do
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
reraise ExUnit.AssertionError, [message: failed, doctest: doctest], stack
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
failed = "Doctest failed: expected exception #{inspect(exception)} but nothing was raised"
|
||||
error = [message: failed, doctest: doctest]
|
||||
reraise ExUnit.AssertionError, error, stack
|
||||
@@ -862,7 +855,8 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
defp add_expr(%{exprs: exprs} = test, expr, expected, formatted) do
|
||||
%{test | exprs: [{expr, tag_expected(expected), formatted} | exprs]}
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
%{test | exprs: [{expr, tag_expected(expected), doctest} | exprs]}
|
||||
end
|
||||
|
||||
defp tag_expected(string) do
|
||||
@@ -876,7 +870,7 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
_ ->
|
||||
if inspectable?(string) do
|
||||
{:inspect, inspect(string)}
|
||||
{:inspect, string}
|
||||
else
|
||||
{:test, string}
|
||||
end
|
||||
|
||||
@@ -577,7 +577,7 @@ defmodule ExUnit.DocTestTest do
|
||||
Doctest failed
|
||||
doctest:
|
||||
iex> :oops
|
||||
"#Inspect<[]>"
|
||||
#Inspect<[]>
|
||||
code: inspect(:oops) === "#Inspect<[]>"
|
||||
left: ":oops"
|
||||
right: "#Inspect<[]>"
|
||||
@@ -600,7 +600,7 @@ defmodule ExUnit.DocTestTest do
|
||||
Doctest failed: expected exception WhatIsThis but got RuntimeError with message "oops"
|
||||
doctest:
|
||||
iex> raise "oops"
|
||||
** (WhatIsThis) "oops"
|
||||
** (WhatIsThis) oops
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 18}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
@@ -615,7 +615,7 @@ defmodule ExUnit.DocTestTest do
|
||||
"oops"
|
||||
doctest:
|
||||
iex> raise "oops"
|
||||
** (RuntimeError) "hello"
|
||||
** (RuntimeError) hello
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 21}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
@@ -635,18 +635,17 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
9) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
9) doctest ExUnit.DocTestTest.Invalid.b/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
|
||||
#{line_placeholder(starting_line + 57)} |
|
||||
#{starting_line + 57} | ```
|
||||
#{line_placeholder(starting_line + 57)} | ^
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 33)} |
|
||||
#{starting_line + 33} | 1 + * 1
|
||||
#{line_placeholder(starting_line + 33)} | ^
|
||||
doctest:
|
||||
iex> 1 + 2
|
||||
3
|
||||
```
|
||||
iex> 1 + * 1
|
||||
1
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
@@ -680,7 +679,22 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
12) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
12) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
|
||||
#{line_placeholder(starting_line + 57)} |
|
||||
#{starting_line + 57} | ```
|
||||
#{line_placeholder(starting_line + 57)} | ^
|
||||
doctest:
|
||||
iex> 1 + 2
|
||||
3
|
||||
```
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
13) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (UnicodeConversionError) invalid encoding starting at <<255, 34, 41>>
|
||||
stacktrace:
|
||||
@@ -688,7 +702,7 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
13) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
14) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (14) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:#{starting_line + 69}:7: missing terminator: } (for "{" starting at line #{starting_line + 69})
|
||||
#{line_placeholder(starting_line + 69)} |
|
||||
@@ -702,28 +716,8 @@ defmodule ExUnit.DocTestTest do
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 69}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ "14) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
|
||||
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
|
||||
|
||||
assert output =~
|
||||
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
|
||||
|
||||
assert output =~ """
|
||||
15) doctest ExUnit.DocTestTest.Invalid.b/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 33)} |
|
||||
#{starting_line + 33} | 1 + * 1
|
||||
#{line_placeholder(starting_line + 33)} | ^
|
||||
doctest:
|
||||
iex> 1 + * 1
|
||||
1
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
16) doctest ExUnit.DocTestTest.Invalid.t/0 (16) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
15) doctest ExUnit.DocTestTest.Invalid.t/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 75}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 75)} |
|
||||
@@ -735,6 +729,12 @@ defmodule ExUnit.DocTestTest do
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 75}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ "16) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
|
||||
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
|
||||
|
||||
assert output =~
|
||||
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
|
||||
end
|
||||
|
||||
test "pattern matching assertions in doctests" do
|
||||
@@ -900,24 +900,7 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "fails when testing single function not found" do
|
||||
message = """
|
||||
test/ex_unit/doc_test_test\.exs: undefined or private function given to doctest:
|
||||
|
||||
ExUnit.DocTestTest.SomewhatGoodModuleWithOnly.three/0
|
||||
|
||||
"""
|
||||
|
||||
assert_raise ExUnit.DocTest.Error, message, fn ->
|
||||
defmodule NeverCompiled do
|
||||
import ExUnit.DocTest
|
||||
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [three: 0], import: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "fails when testing multiple functions not found" do
|
||||
test "fails when testing functions are not found" do
|
||||
message = """
|
||||
test/ex_unit/doc_test_test\.exs: undefined or private functions given to doctest:
|
||||
|
||||
@@ -989,6 +972,16 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "doctest direct invocation" do
|
||||
defmodule Direct do
|
||||
use ExUnit.Case, register: false
|
||||
doctest ExUnit.DocTestTest.GoodModule, import: true
|
||||
end
|
||||
|
||||
[head | _] = Direct.__ex_unit__().tests
|
||||
assert apply(Direct, head.name, [%{}]) == {:ok, 2}
|
||||
end
|
||||
|
||||
defp line_placeholder(line_number) do
|
||||
digits =
|
||||
line_number
|
||||
|
||||
@@ -523,7 +523,10 @@ defmodule IEx.Helpers do
|
||||
:code.get_path()
|
||||
|> Protocol.extract_protocols()
|
||||
|> Enum.uniq()
|
||||
|> Enum.reject(fn protocol -> is_nil(protocol.impl_for(term)) end)
|
||||
|> Enum.filter(fn protocol ->
|
||||
Code.ensure_loaded?(protocol) and function_exported?(protocol, :impl_for, 1) and
|
||||
protocol.impl_for(term) != nil
|
||||
end)
|
||||
|> Enum.sort()
|
||||
|> Enum.map_join(", ", &inspect/1)
|
||||
end
|
||||
|
||||
@@ -374,9 +374,29 @@ defmodule IEx.Server do
|
||||
|
||||
defp io_get(pid, prompt_type, prefix, counter) do
|
||||
prompt = prompt(prompt_type, prefix, counter)
|
||||
send(pid, {:input, self(), IO.gets(:stdio, prompt)})
|
||||
send(pid, {:input, self(), io_get(prompt)})
|
||||
end
|
||||
|
||||
defp io_get(prompt) do
|
||||
pid = Process.group_leader()
|
||||
ref = Process.monitor(pid)
|
||||
command = {:get_until, :unicode, prompt, __MODULE__, :__io_get__, []}
|
||||
send(pid, {:io_request, self(), ref, command})
|
||||
|
||||
receive do
|
||||
{:io_reply, ^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
reply
|
||||
|
||||
{:DOWN, ^ref, _, _, _} ->
|
||||
{:error, :terminated}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __io_get__([], :eof), do: {:done, :eof, []}
|
||||
def __io_get__([], chars), do: {:done, List.to_string(chars), []}
|
||||
|
||||
defp prompt(prompt_type, prefix, counter) do
|
||||
{mode, prefix} =
|
||||
if Node.alive?() do
|
||||
|
||||
@@ -31,9 +31,9 @@ defmodule IEx.AutocompleteTest do
|
||||
end
|
||||
|
||||
test "Erlang module multiple values completion" do
|
||||
{:yes, '', list} = expand(':user')
|
||||
assert 'user' in list
|
||||
assert 'user_drv' in list
|
||||
{:yes, '', list} = expand(':logger')
|
||||
assert 'logger' in list
|
||||
assert 'logger_proxy' in list
|
||||
end
|
||||
|
||||
test "Erlang root completion" do
|
||||
|
||||
@@ -126,8 +126,13 @@ defmodule IEx.InteractionTest do
|
||||
test "inspect opts" do
|
||||
opts = [inspect: [binaries: :as_binaries, charlists: :as_lists, structs: false, limit: 4]]
|
||||
|
||||
assert capture_iex("<<45, 46, 47>>\n[45, 46, 47]\n%IO.Stream{}", opts) ==
|
||||
"<<45, 46, 47>>\n[45, 46, 47]\n%{__struct__: IO.Stream, device: nil, line_or_bytes: :line, raw: true}"
|
||||
assert "<<45, 46, 47>>\n[45, 46, 47]\n%{" <> map =
|
||||
capture_iex("<<45, 46, 47>>\n[45, 46, 47]\n%IO.Stream{}", opts)
|
||||
|
||||
assert map =~ "__struct__: IO.Stream"
|
||||
assert map =~ "device: nil"
|
||||
assert map =~ "line_or_bytes: :line"
|
||||
assert map =~ "raw: true"
|
||||
end
|
||||
|
||||
test "exception" do
|
||||
|
||||
@@ -134,7 +134,8 @@ defmodule Logger.Utils do
|
||||
pad_char: ?\s,
|
||||
precision: :none,
|
||||
strings: true,
|
||||
width: :none
|
||||
width: :none,
|
||||
maps_order: :undefined
|
||||
}
|
||||
|
||||
defp handle_format_spec(%{control_char: char} = spec, opts) when char in 'wWpP' do
|
||||
|
||||
@@ -772,12 +772,13 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates Supervisor reports start error" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
children = [worker(__MODULE__, [], function: :error)]
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
|
||||
assert {:error, {:shutdown, {:failed_to_start_child, __MODULE__, :stop}}} =
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
|
||||
\*\* \(exit\) :stop
|
||||
@@ -786,12 +787,13 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates Supervisor reports start error with raise" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
children = [worker(__MODULE__, [], function: :undef)]
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
|
||||
assert {:error, {:shutdown, {:failed_to_start_child, __MODULE__, {:EXIT, _}}}} =
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
|
||||
\*\* \(exit\) an exception was raised:
|
||||
@@ -804,12 +806,12 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates Supervisor reports terminated" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
children = [worker(Task, [Kernel, :exit, [:stop]])]
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one, max_restarts: 0)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) terminated
|
||||
\*\* \(exit\) :stop
|
||||
@@ -827,12 +829,12 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates Supervisor reports max restarts shutdown" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
children = [worker(Task, [Kernel, :exit, [:stop]])]
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one, max_restarts: 0)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) caused shutdown
|
||||
\*\* \(exit\) :reached_max_restart_intensity
|
||||
@@ -888,14 +890,14 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates DynamicSupervisor reports abnormal shutdown" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
|
||||
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one)
|
||||
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
|
||||
Process.exit(pid, :normal)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child :undefined of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shut down abnormally
|
||||
\*\* \(exit\) :stop
|
||||
@@ -909,9 +911,9 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates DynamicSupervisor reports abnormal shutdown including extra_arguments" do
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
{:ok, pid} =
|
||||
DynamicSupervisor.start_link(strategy: :one_for_one, extra_arguments: [:extra])
|
||||
|
||||
@@ -919,7 +921,6 @@ defmodule Logger.TranslatorTest do
|
||||
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
|
||||
Process.exit(pid, :normal)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child :undefined of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shut down abnormally
|
||||
\*\* \(exit\) :stop
|
||||
@@ -933,14 +934,14 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates named DynamicSupervisor reports abnormal shutdown" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
|
||||
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one, name: __MODULE__)
|
||||
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
|
||||
Process.exit(pid, :normal)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child :undefined of Supervisor Logger.TranslatorTest shut down abnormally
|
||||
\*\* \(exit\) :stop
|
||||
@@ -954,11 +955,11 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates :supervisor_bridge progress" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
{:ok, pid} = :supervisor_bridge.start_link(MyBridge, :normal)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[info\] Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) started
|
||||
Pid: #PID<\d+\.\d+\.\d+>
|
||||
@@ -967,11 +968,11 @@ defmodule Logger.TranslatorTest do
|
||||
end
|
||||
|
||||
test "translates :supervisor_bridge reports" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
{:ok, pid} = :supervisor_bridge.start_link(MyBridge, :stop)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) terminated
|
||||
\*\* \(exit\) :stop
|
||||
|
||||
@@ -107,17 +107,18 @@ defmodule Mix.Compilers.ApplicationTracer do
|
||||
:ets.delete(table, module)
|
||||
end
|
||||
|
||||
warnings =
|
||||
{warnings, _} =
|
||||
:ets.foldl(
|
||||
fn {module, function, arity, env_module, env_function, env_file, env_line}, acc ->
|
||||
fn {module, function, arity, env_module, env_function, env_file, env_line},
|
||||
{acc, cache} ->
|
||||
# If the module is preloaded, it is always available, so we skip it.
|
||||
# If the module is non existing, the compiler will warn, so we skip it.
|
||||
# If the module belongs to this application (from another compiler), we skip it.
|
||||
# If the module is excluded, we skip it.
|
||||
with path when is_list(path) <- :code.which(module),
|
||||
{:ok, module_app} <- app(path),
|
||||
true <- module_app != app,
|
||||
false <- module in excludes or {module, function, arity} in excludes do
|
||||
{module_app, cache} = app_for_module(module, cache)
|
||||
|
||||
if module_app != nil and module_app != app and module not in excludes and
|
||||
{module, function, arity} not in excludes do
|
||||
env_mfa =
|
||||
if env_function do
|
||||
{env_module, elem(env_function, 0), elem(env_function, 1)}
|
||||
@@ -126,12 +127,12 @@ defmodule Mix.Compilers.ApplicationTracer do
|
||||
end
|
||||
|
||||
warning = {:undefined_app, module_app, module, function, arity}
|
||||
[{__MODULE__, warning, {env_file, env_line, env_mfa}} | acc]
|
||||
{[{__MODULE__, warning, {env_file, env_line, env_mfa}} | acc], cache}
|
||||
else
|
||||
_ -> acc
|
||||
{acc, cache}
|
||||
end
|
||||
end,
|
||||
[],
|
||||
{[], %{}},
|
||||
table
|
||||
)
|
||||
|
||||
@@ -140,15 +141,44 @@ defmodule Mix.Compilers.ApplicationTracer do
|
||||
|> Module.ParallelChecker.emit_warnings()
|
||||
end
|
||||
|
||||
# ../elixir/ebin/elixir.beam -> elixir
|
||||
# ../ssl-9.6/ebin/ssl.beam -> ssl
|
||||
defp app(path) do
|
||||
case path |> Path.split() |> Enum.take(-3) do
|
||||
[dir, "ebin", _beam] -> {:ok, dir |> String.split("-") |> hd()}
|
||||
_ -> :error
|
||||
defp app_for_module(module, cache) do
|
||||
case cache do
|
||||
%{^module => maybe_app} ->
|
||||
{maybe_app, cache}
|
||||
|
||||
%{} ->
|
||||
maybe_app = app_for_module(module)
|
||||
{maybe_app, Map.put(cache, module, maybe_app)}
|
||||
end
|
||||
end
|
||||
|
||||
# ../elixir/ebin/elixir.beam -> elixir
|
||||
# ../ssl-9.6/ebin/ssl.beam -> ssl
|
||||
defp app_for_module(module) do
|
||||
with [_ | _] = path when is_list(path) <- :code.which(module),
|
||||
[_ | _] = app <-
|
||||
path |> Enum.reverse() |> discard_dir() |> discard_ebin() |> collect_dir([]) do
|
||||
List.to_string(app)
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp discard_dir([?\\ | path]), do: path
|
||||
defp discard_dir([?/ | path]), do: path
|
||||
defp discard_dir([_ | path]), do: discard_dir(path)
|
||||
defp discard_dir([]), do: []
|
||||
|
||||
defp discard_ebin(~c"nibe/" ++ path), do: path
|
||||
defp discard_ebin(~c"nibe\\" ++ path), do: path
|
||||
defp discard_ebin(_), do: []
|
||||
|
||||
defp collect_dir([?\\ | _], acc), do: acc
|
||||
defp collect_dir([?/ | _], acc), do: acc
|
||||
defp collect_dir([?- | path], _acc), do: collect_dir(path, [])
|
||||
defp collect_dir([head | path], acc), do: collect_dir(path, [head | acc])
|
||||
defp collect_dir([], acc), do: acc
|
||||
|
||||
def stop(pending_save_manifest \\ nil) do
|
||||
try do
|
||||
:ets.delete(warnings_table())
|
||||
|
||||
@@ -465,7 +465,11 @@ defmodule Mix.Compilers.Elixir do
|
||||
|
||||
modules_set = Map.from_keys(modules, true)
|
||||
{_, runtime_modules} = fixpoint_runtime_modules(sources, modules_set)
|
||||
{:runtime, runtime_modules, warnings}
|
||||
|
||||
runtime_paths =
|
||||
Enum.map(runtime_modules, &{&1, Path.join(compile_path, Atom.to_string(&1) <> ".beam")})
|
||||
|
||||
{:runtime, runtime_paths, warnings}
|
||||
else
|
||||
Mix.Utils.compiling_n(length(changed), :ex)
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ defmodule Mix.Tasks.Format do
|
||||
|
||||
args
|
||||
|> expand_args(dot_formatter, formatter_opts_and_subs, opts)
|
||||
|> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: 30000)
|
||||
|> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: :infinity)
|
||||
|> Enum.reduce({[], []}, &collect_status/2)
|
||||
|> check!()
|
||||
end
|
||||
@@ -551,7 +551,9 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
|
||||
defp stdin_or_wildcard("-"), do: [:stdin]
|
||||
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
|
||||
|
||||
defp stdin_or_wildcard(path),
|
||||
do: path |> Path.expand() |> Path.wildcard(match_dot: true) |> Enum.filter(&File.regular?/1)
|
||||
|
||||
defp elixir_format(content, formatter_opts) do
|
||||
case Code.format_string!(content, formatter_opts) do
|
||||
|
||||
@@ -173,6 +173,7 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
|
||||
if apps_paths = Mix.Project.apps_paths(config) do
|
||||
build_path = Mix.Project.build_path(config)
|
||||
apps_paths = Enum.sort(apps_paths)
|
||||
|
||||
compile_paths =
|
||||
Enum.map(apps_paths, fn {app, _} ->
|
||||
|
||||
@@ -654,13 +654,31 @@ defmodule Mix.Utils do
|
||||
headers = [{'user-agent', 'Mix/#{System.version()}'}]
|
||||
request = {:binary.bin_to_list(path), headers}
|
||||
|
||||
# Use the system certificates if available, otherwise skip peer verification
|
||||
# TODO: Always use system certificates when OTP >= 25.1 is required
|
||||
ssl_options =
|
||||
if Code.ensure_loaded?(:httpc) and function_exported?(:httpc, :ssl_verify_host_options, 1) do
|
||||
try do
|
||||
apply(:httpc, :ssl_verify_host_options, [true])
|
||||
rescue
|
||||
_ ->
|
||||
Mix.shell().error(
|
||||
"warning: failed to load system certificates. SSL peer verification will be skipped but downloads are still verified with a checksum"
|
||||
)
|
||||
|
||||
[verify: :verify_none]
|
||||
end
|
||||
else
|
||||
[verify: :verify_none]
|
||||
end
|
||||
|
||||
# We are using relaxed: true because some servers is returning a Location
|
||||
# header with relative paths, which does not follow the spec. This would
|
||||
# cause the request to fail with {:error, :no_scheme} unless :relaxed
|
||||
# is given.
|
||||
#
|
||||
# If a proxy environment variable was supplied add a proxy to httpc.
|
||||
http_options = [relaxed: true] ++ proxy_config(path)
|
||||
http_options = [relaxed: true, ssl: ssl_options] ++ proxy_config(path)
|
||||
|
||||
# Silence the warning from OTP as we verify the contents
|
||||
level = Logger.level()
|
||||
|
||||
@@ -488,6 +488,10 @@ defmodule Mix.DepTest do
|
||||
Mix.RemoteConverger.register(nil)
|
||||
end
|
||||
|
||||
defp sorted_keys(map) do
|
||||
map |> Map.keys() |> Enum.sort()
|
||||
end
|
||||
|
||||
test "deps_paths" do
|
||||
deps = [
|
||||
{:abc_repo, "0.1.0", path: "custom/abc_repo"},
|
||||
@@ -502,33 +506,42 @@ defmodule Mix.DepTest do
|
||||
[:abc_repo, :git_repo, :deps_repo]
|
||||
]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(depth: 1)) == [:abc_repo, :deps_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(depth: 2)) == [:abc_repo, :deps_repo, :git_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(depth: 3)) == [:abc_repo, :deps_repo, :git_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(depth: 1)) == [:abc_repo, :deps_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(depth: 2)) == [:abc_repo, :deps_repo, :git_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(depth: 3)) == [:abc_repo, :deps_repo, :git_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo])) == [:abc_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo])) == [:deps_repo, :git_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo])) == [:git_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo])) == [:abc_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 1)) == [:abc_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 1)) == [:deps_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 1)) == [:git_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:deps_repo])) == [
|
||||
:deps_repo,
|
||||
:git_repo
|
||||
]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 2)) == [:abc_repo]
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 2)) == [:git_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:git_repo])) == [:git_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 2)) ==
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 1)) == [:abc_repo]
|
||||
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 1)) == [
|
||||
:deps_repo
|
||||
]
|
||||
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 1)) == [:git_repo]
|
||||
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 2)) == [:abc_repo]
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 2)) == [:git_repo]
|
||||
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 2)) ==
|
||||
[:deps_repo, :git_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo])) ==
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo])) ==
|
||||
[:abc_repo, :deps_repo, :git_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 1)) ==
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 1)) ==
|
||||
[:abc_repo, :deps_repo]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 2)) ==
|
||||
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 2)) ==
|
||||
[:abc_repo, :deps_repo, :git_repo]
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -81,6 +81,16 @@ defmodule Mix.Tasks.FormatTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "does not try to format a directory that matches a given pattern", context do
|
||||
in_tmp(context.test, fn ->
|
||||
File.mkdir_p!("a.ex")
|
||||
|
||||
assert_raise Mix.Error, ~r"Could not find a file to format", fn ->
|
||||
Mix.Tasks.Format.run(["*.ex"])
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "reads file from stdin and prints to stdout", context do
|
||||
in_tmp(context.test, fn ->
|
||||
File.write!("a.ex", """
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user