diff options
Diffstat (limited to 'lib/tools')
41 files changed, 2639 insertions, 1330 deletions
diff --git a/lib/tools/c_src/Makefile.in b/lib/tools/c_src/Makefile.in index e6b76e2238..65a7f5f424 100644 --- a/lib/tools/c_src/Makefile.in +++ b/lib/tools/c_src/Makefile.in @@ -128,12 +128,13 @@ EMEM_ERTS_LIB=erts_r$(TYPEMARKER) endif +EMEM_ETHR_LIBS=$(subst -l$(ETHR_LIB_NAME),-l$(ETHR_LIB_NAME)$(TYPEMARKER),$(subst -lerts_internal_r,-lerts_internal_r$(TYPEMARKER),$(ETHR_LIBS))) + EMEM_LIBS = $(LIBS) \ -L$(ERL_TOP)/erts/lib/$(TARGET) \ -L$(ERL_TOP)/erts/lib/internal/$(TARGET) \ -l$(EMEM_ERTS_LIB) \ - -l$(ETHR_LIB_NAME)$(TYPEMARKER) \ - $(ETHR_X_LIBS) + $(EMEM_ETHR_LIBS) EMEM_OBJS = $(addprefix $(EMEM_OBJ_DIR)/,$(notdir $(EMEM_SRCS:.c=.o))) diff --git a/lib/tools/c_src/erl_memory.c b/lib/tools/c_src/erl_memory.c index a0e139f059..872d55e789 100644 --- a/lib/tools/c_src/erl_memory.c +++ b/lib/tools/c_src/erl_memory.c @@ -1,22 +1,22 @@ -/* ``The contents of this file are subject to the Erlang Public License, +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2003-2010. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be - * retrieved via the world wide web at http://www.erlang.org/. - * + * retrieved online at http://www.erlang.org/. + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * - * The Initial Developer of the Original Code is Ericsson Utvecklings AB. - * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings - * AB. All Rights Reserved.'' - * - * $Id$ + * + * %CopyrightEnd% */ - /* * Description: * @@ -281,17 +281,13 @@ mutex_destroy(ethr_mutex *mtx) static INLINE void mutex_lock(ethr_mutex *mtx) { - int res = ethr_mutex_lock(mtx); - if (res) - error_msg(res, "Mutex lock"); + ethr_mutex_lock(mtx); } static INLINE void mutex_unlock(ethr_mutex *mtx) { - int res = ethr_mutex_unlock(mtx); - if (res) - error_msg(res, "Mutex unlock"); + ethr_mutex_unlock(mtx); } static INLINE void @@ -314,16 +310,14 @@ static INLINE void cond_wait(ethr_cond *cnd, ethr_mutex *mtx) { int res = ethr_cond_wait(cnd, mtx); - if (res) + if (res != 0 && res != EINTR) error_msg(res, "Cond wait"); } static INLINE void cond_signal(ethr_cond *cnd) { - int res = ethr_cond_signal(cnd); - if (res) - error_msg(res, "Cond signal"); + ethr_cond_signal(cnd); } @@ -2774,7 +2768,7 @@ main(int argc, char *argv[]) exit(1); } - if (ethr_init(NULL) != 0) { + if (ethr_init(NULL) != 0 || ethr_late_init(NULL) != 0) { fprintf(stderr, "emem: failed to initialize thread package\n"); exit(1); } diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml index 323bd0dda8..0a3302bda5 100644 --- a/lib/tools/doc/src/cover.xml +++ b/lib/tools/doc/src/cover.xml @@ -270,6 +270,8 @@ defaults to <c>function</c>.</p> <p>If <c>Module</c> is not Cover compiled, the function returns <c>{error,{not_cover_compiled,Module}}</c>.</p> + <p>HINT: It is possible to issue multiple analyse_to_file commands at + the same time. </p> </desc> </func> <func> @@ -307,6 +309,33 @@ <c>.beam</c> file, or in <c>../src</c> relative to that directory. If no source code is found, <c>,{error,no_source_code_found}</c> is returned.</p> + <p>HINT: It is possible to issue multiple analyse_to_file commands at + the same time. </p> + </desc> + </func> + <func> + <name>async_analyse_to_file(Module) -> </name> + <name>async_analyse_to_file(Module,Options) -> </name> + <name>async_analyse_to_file(Module, OutFile) -> </name> + <name>async_analyse_to_file(Module, OutFile, Options) -> pid()</name> + <fsummary>Asynchronous call to analyse_to_file.</fsummary> + <type> + <v>Module = atom()</v> + <v>OutFile = string()</v> + <v>Options = [Option]</v> + <v>Option = html</v> + <v>Error = {not_cover_compiled,Module} | {file,File,Reason} | no_source_code_found | not_main_node</v> + <v> File = string()</v> + <v> Reason = term()</v> + </type> + <desc> + <p>This function works exactly the same way as + <seealso marker="#analyse_to_file-1">analyse_to_file</seealso> except + that it is asynchronous instead of synchronous. The spawned process + will link with the caller when created. If an <c>Error</c> occurs + while doing the cover analysis the process will crash with the same + error reason as <seealso marker="#analyse_to_file-1">analyse_to_file</seealso> + would return.</p> </desc> </func> <func> diff --git a/lib/tools/doc/src/cover_chapter.xml b/lib/tools/doc/src/cover_chapter.xml index b4f7919183..92a790c34e 100644 --- a/lib/tools/doc/src/cover_chapter.xml +++ b/lib/tools/doc/src/cover_chapter.xml @@ -403,6 +403,13 @@ ok database contains information about each executable line in each Cover compiled module, performance decreases proportionally to the size and number of the Cover compiled modules.</p> + <p>To improve performance when analysing cover results it is possible + to do multiple calls to <seealso marker="cover#analyse-1">analyse</seealso> + and <seealso marker="cover#analyse_to_file-1">analyse_to_file</seealso> + at once. You can also use the + <seealso marker="cover#async_analyse_to_file-1">async_analyse_to_file</seealso> + convenience function. + </p> </section> <section> diff --git a/lib/tools/doc/src/eprof.xml b/lib/tools/doc/src/eprof.xml index ae1033f2d0..6d68c90768 100644 --- a/lib/tools/doc/src/eprof.xml +++ b/lib/tools/doc/src/eprof.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2009</year> + <year>1996</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>eprof</title> @@ -35,8 +35,7 @@ used. The profiling is done using the Erlang trace BIFs. Tracing of local function calls for a specified set of processes is enabled when profiling is begun, and disabled when profiling is stopped.</p> - <p>When using Eprof, expect a significant slowdown in program execution, - in most cases at least 100 percent.</p> + <p>When using Eprof expect a slowdown in program execution.</p> </description> <funcs> <func> @@ -47,15 +46,19 @@ <v>Reason = {already_started,Pid}</v> </type> <desc> - <p>Starts the Eprof server which owns the Eprof internal database.</p> + <p>Starts the Eprof server which holds the internal state of the collected data.</p> </desc> </func> <func> - <name>start_profiling(Rootset) -> profiling | error</name> - <name>profile(Rootset) -> profiling | error</name> + <name>start_profiling(Rootset) -> profiling | {error, Reason}</name> + <name>start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name> <fsummary>Start profiling.</fsummary> <type> <v>Rootset = [atom() | pid()]</v> + <v>Pattern = {Module, Function, Arity}</v> + <v>Module = Function = atom()</v> + <v>Arity = integer()</v> + <v>Reason = term()</v> </type> <desc> <p>Starts profiling for the processes in <c>Rootset</c> (and any new @@ -64,6 +67,9 @@ <p><c>Rootset</c> is a list of pids and registered names.</p> <p>The function returns <c>profiling</c> if tracing could be enabled for all processes in <c>Rootset</c>, or <c>error</c> otherwise.</p> + <p>A pattern can be selected to narrow the profiling. For instance ca a specific + module be selected and only the code processes executes in that module will be + profiled.</p> </desc> </func> <func> @@ -75,14 +81,20 @@ </desc> </func> <func> - <name>profile(Rootset,Fun) -> {ok,Value} | {error,Reason} | error</name> - <name>profile(Rootset,Module,Function,Args) -> {ok,Value} | {error,Reason} | error</name> + <name>profile(Fun) -> profiling | {error, Reason}</name> + <name>profile(Rootset) -> profiling | {error, Reason}</name> + <name>profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name> + <name>profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name> + <name>profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name> + <name>profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name> <fsummary>Start profiling.</fsummary> <type> <v>Rootset = [atom() | pid()]</v> <v>Fun = fun() -> term()</v> + <v>Pattern = {Module, Function, Arity}</v> <v>Module = Function = atom()</v> <v>Args = [term()]</v> + <v>Arity = integer()</v> <v>Value = Reason = term()</v> </type> <desc> @@ -96,7 +108,7 @@ <c>Rootset</c>, the function returns <c>{ok,Value}</c> when <c>Fun()</c>/<c>apply</c> returns with the value <c>Value</c>, or <c>{error,Reason}</c> if <c>Fun()</c>/<c>apply</c> fails with - exit reason <c>Reason</c>. Otherwise it returns <c>error</c> + exit reason <c>Reason</c>. Otherwise it returns <c>{error, Reason}</c> immediately.</p> <p>The programmer must ensure that the function given as argument is truly synchronous and that no work continues after @@ -104,7 +116,15 @@ </desc> </func> <func> - <name>analyse()</name> + <name>analyze() -> ok</name> + <name>analyze(Type) -> ok</name> + <name>analyze(Type,Options) -> ok</name> + <type> + <v>Type = procs | total</v> + <v>Options = [{filter, Filter} | {sort, Sort}</v> + <v>Filter = [{calls, integer()} | {time, float()}]</v> + <v>Sort = time | calls | mfa</v> + </type> <fsummary>Display profiling results per process.</fsummary> <desc> <p>Call this function when profiling has been stopped to display @@ -113,17 +133,10 @@ <item>how much time has been used by each process, and</item> <item>in which function calls this time has been spent.</item> </list> - <p>Time is shown as percentage of total time, not as absolute time.</p> - </desc> - </func> - <func> - <name>total_analyse()</name> - <fsummary>Display profiling results per function call.</fsummary> - <desc> - <p>Call this function when profiling has been stopped to display + <p>Call <c>analyze</c> with <c>total</c> option when profiling has been stopped to display the results per function call, that is in which function calls the time has been spent.</p> - <p>Time is shown as percentage of total time, not as absolute time.</p> + <p>Time is shown as percentage of total time and as absolute time.</p> </desc> </func> <func> diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml index 912c442153..c21afc1f9b 100644 --- a/lib/tools/doc/src/erlang_mode.xml +++ b/lib/tools/doc/src/erlang_mode.xml @@ -173,7 +173,7 @@ sum(L) -> sum(L, 0). sum([H|T], Sum) -> sum(T, Sum + H); - sum([], Sum) -> Sum."</code> + sum([], Sum) -> Sum.</code> </item> </list> </section> diff --git a/lib/tools/doc/src/erlang_mode_chapter.xml b/lib/tools/doc/src/erlang_mode_chapter.xml index b22c6b1809..8aabd6ae74 100644 --- a/lib/tools/doc/src/erlang_mode_chapter.xml +++ b/lib/tools/doc/src/erlang_mode_chapter.xml @@ -45,7 +45,7 @@ <section> <title>Elisp</title> - <p>There are two Elsip modules include in this tool package + <p>There are two Elisp modules included in this tool package for Emacs. There is erlang.el that defines the actual erlang mode and there is erlang-start.el that makes some nice initializations.</p> </section> diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index e7d1ae150c..5f9cecd6e4 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -30,6 +30,112 @@ </header> <p>This document describes the changes made to the Tools application.</p> +<section><title>Tools 2.6.6.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>eprof: API sort mismatch has now been fixed. </p> + <p> + Own Id: OTP-8853</p> + </item> + <item> + <p> + eprof: fix division by zero in statistics</p> + <p> + Own Id: OTP-8963</p> + </item> + </list> + </section> + +</section> + +<section><title>Tools 2.6.6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + <c>cover</c> will now show ampersand characters in the + source code correctly. (Thanks to Tom Moertel.)</p> + <p> + Own Id: OTP-8776</p> + </item> + </list> + </section> + +</section> + +<section><title>Tools 2.6.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>A race condition affecting Cover has been removed.</p> + <p> + Own Id: OTP-8469</p> + </item> + <item> + <p> + Emacs improvements:</p> + <p> + Fixed emacs-mode installation problems.</p> + <p> + Fixed a couple of -spec and -type indentation and + font-lock problems.</p> + <p> + Fixed error messages on emacs-21.</p> + <p> + Magnus Henoch fixed several issues.</p> + <p> + Ralf Doering, Klas Johansson and Chris Bernard + contributed various emacs-eunit improvements.</p> + <p> + Klas Johansson and Dave Peticolas added emacs-flymake + support.</p> + <p> + Own Id: OTP-8530</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>Xref has been updated to use the <c>re</c> module + instead of the deprecated <c>regexp</c> module.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-8472</p> + </item> + <item> + <p>When given the option <c>{builtins,true}</c> Xref now + adds calls to operators.</p> + <p> + Own Id: OTP-8647</p> + </item> + <item> + <p><c>eprof</c> has been reimplemented with support in + the Erlang virtual machine and is now both faster (i.e. + slows down the code being measured less) and scales much + better. In measurements we saw speed-ups compared to the + old eprof ranging from 6 times (for sequential code that + only uses one scheduler/core) up to 84 times (for + parallel code that uses 8 cores).</p> + <p>Note: The API for the <c>eprof</c> has been cleaned up + and extended. See the documentation.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-8706</p> + </item> + </list> + </section> + +</section> + <section><title>Tools 2.6.5.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml index 407a7392ad..75ffa25311 100644 --- a/lib/tools/doc/src/xref.xml +++ b/lib/tools/doc/src/xref.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2000</year><year>2009</year> + <year>2000</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>xref</title> @@ -239,7 +239,7 @@ represented by <item>RegArity ::= RegString | Number | <c>_</c> | <c>-1</c></item> <item>RegAtom ::= RegString | Atom | <c>_</c></item> <item>RegString ::= - a regular expression, as described in the - <c>regexp</c> module, enclosed in double quotes -</item> + <c>re</c> module, enclosed in double quotes -</item> <item>Type ::= <c>Fun</c> | <c>Mod</c> | <c>App</c> | <c>Rel</c></item> <item>Function ::= Atom</item> <item>Application ::= Atom</item> @@ -264,8 +264,7 @@ represented by Assigning a type to a list or tuple of <c>Constant</c> is equivalent to assigning the type to each <c>Constant</c>. </p> - <p> <marker id="regexp"></marker> -<em>Regular expressions</em> are used as a + <p><marker id="regexp"></marker><em>Regular expressions</em> are used as a means to select some of the vertices of a graph. A <c>RegExpr</c> consisting of a <c>RegString</c> and a type - an example is <c>"xref_.*" : Mod</c> - is interpreted as those @@ -1546,8 +1545,11 @@ Evaluates a predefined analysis. </funcs> <section> - <title>See Also</title> - <p>beam_lib(3), digraph(3), digraph_utils(3), regexp(3), + <title>See Also</title><p> + <seealso marker="stdlib:beam_lib">beam_lib(3)</seealso>, + <seealso marker="stdlib:digraph">digraph(3)</seealso>, + <seealso marker="stdlib:digraph_utils">digraph_utils(3)</seealso>, + <seealso marker="stdlib:re">re(3)</seealso>, <seealso marker="xref_chapter">TOOLS User's Guide</seealso></p> </section> </erlref> diff --git a/lib/tools/emacs/Makefile b/lib/tools/emacs/Makefile index 7249263992..8533488463 100644 --- a/lib/tools/emacs/Makefile +++ b/lib/tools/emacs/Makefile @@ -37,8 +37,12 @@ MAN_FILES= \ tags.3 EMACS_FILES= \ + erlang-skels \ + erlang-skels-old \ + erlang_appwiz \ erlang-start \ erlang-eunit \ + erlang-flymake \ erlang README_FILES= README diff --git a/lib/tools/emacs/README b/lib/tools/emacs/README index ca068d04c4..cc107dcd41 100644 --- a/lib/tools/emacs/README +++ b/lib/tools/emacs/README @@ -42,7 +42,14 @@ Files\erl-<Ver>: (setq erlang-root-dir "C:/Program Files/erl<Ver>") (setq exec-path (cons "C:/Program Files/erl<Ver>/bin" exec-path)) (require 'erlang-start) - +Miscellaneous addons +-------------------- + +In order to check erlang source code on the fly, add the following +line to your .emacs file (after erlang-start, see above). See +erlang-flymake.el for more information on how to customize the syntax +check. + (require 'erlang-flymake) diff --git a/lib/tools/emacs/erlang-eunit.el b/lib/tools/emacs/erlang-eunit.el index 05528aee6d..f2c0db67dd 100644 --- a/lib/tools/emacs/erlang-eunit.el +++ b/lib/tools/emacs/erlang-eunit.el @@ -1,27 +1,44 @@ ;; ;; %CopyrightBegin% -;; -;; Copyright Ericsson AB 2009. All Rights Reserved. -;; +;; +;; Copyright Ericsson AB 2009-2010. All Rights Reserved. +;; ;; The contents of this file are subject to the Erlang Public License, ;; Version 1.1, (the "License"); you may not use this file except in ;; compliance with the License. You should have received a copy of the ;; Erlang Public License along with this software. If not, it can be ;; retrieved online at http://www.erlang.org/. -;; +;; ;; Software distributed under the License is distributed on an "AS IS" ;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See ;; the License for the specific language governing rights and limitations ;; under the License. -;; +;; ;; %CopyrightEnd% ;;; ;;; Purpose: Provide EUnit utilities. ;;; ;;; Author: Klas Johansson -(defvar erlang-eunit-separate-src-and-test-directories t - "*Whether or not to keep source and EUnit test files in separate directories") +(eval-when-compile + (require 'cl)) + +(defvar erlang-eunit-src-candidate-dirs '("../src" ".") + "*Name of directories which to search for source files matching +an EUnit test file. The first directory in the list will be used, +if there is no match.") + +(defvar erlang-eunit-test-candidate-dirs '("../test" ".") + "*Name of directories which to search for EUnit test files matching +a source file. The first directory in the list will be used, +if there is no match.") + +(defvar erlang-eunit-autosave nil + "*Set to non-nil to automtically save unsaved buffers before running tests. +This is useful, reducing the save-compile-load-test cycle to one keychord.") + +(defvar erlang-eunit-recent-info '((mode . nil) (module . nil) (test . nil) (cover . nil)) + "Info about the most recent running of an EUnit test representation.") ;;; ;;; Switch between src/EUnit test buffers @@ -41,7 +58,6 @@ buffer and vice versa" "Open the EUnit test file which corresponds to a src file" (find-file-other-window (erlang-eunit-test-filename src-file-path))) - ;;; ;;; Open the src file which corresponds to the an EUnit test file ;;; @@ -52,37 +68,55 @@ buffer and vice versa" ;;; Return the name and path of the EUnit test file ;;, (input may be either the source filename itself or the EUnit test filename) (defun erlang-eunit-test-filename (file-path) - (erlang-eunit-rewrite-filename file-path "test" "_tests")) + (if (erlang-eunit-test-file-p file-path) + file-path + (erlang-eunit-rewrite-filename file-path erlang-eunit-test-candidate-dirs))) ;;; Return the name and path of the source file ;;, (input may be either the source filename itself or the EUnit test filename) (defun erlang-eunit-src-filename (file-path) - (erlang-eunit-rewrite-filename file-path "src" "")) + (if (erlang-eunit-src-file-p file-path) + file-path + (erlang-eunit-rewrite-filename file-path erlang-eunit-src-candidate-dirs))) ;;; Rewrite a filename from the src or test filename to the other -(defun erlang-eunit-rewrite-filename (orig-file-path dest-dirname dest-suffix) - (let* ((root-dir-name (erlang-eunit-file-root-dir-name orig-file-path)) - (src-module-name (erlang-eunit-source-module-name orig-file-path)) - (dest-base-name (concat src-module-name dest-suffix ".erl")) - (dest-dir-name-1 (file-name-directory orig-file-path)) - (dest-dir-name-2 (filename-join root-dir-name dest-dirname)) - (dest-file-name-1 (filename-join dest-dir-name-1 dest-base-name)) - (dest-file-name-2 (filename-join dest-dir-name-2 dest-base-name))) - ;; This function tries to be a bit intelligent: - ;; * if there already is a test (or source) file in the same - ;; directory as a source (or test) file, it'll be picked - ;; * if there already is a test (or source) file in a separate - ;; test (or src) directory, it'll be picked - ;; * otherwise it'll resort to whatever alternative (same or - ;; separate directories) that the user has chosen - (cond ((file-readable-p dest-file-name-1) - dest-file-name-1) - ((file-readable-p dest-file-name-2) - dest-file-name-2) - (erlang-eunit-separate-src-and-test-directories - dest-file-name-2) - (t - dest-file-name-1)))) +(defun erlang-eunit-rewrite-filename (orig-file-path candidate-dirs) + (or (erlang-eunit-locate-buddy orig-file-path candidate-dirs) + (erlang-eunit-buddy-file-path orig-file-path (car candidate-dirs)))) + +;;; Search for a file's buddy file (a source file's EUnit test file, +;;; or an EUnit test file's source file) in a list of candidate +;;; directories. +(defun erlang-eunit-locate-buddy (orig-file-path candidate-dirs) + (when candidate-dirs + (let ((buddy-file-path (erlang-eunit-buddy-file-path + orig-file-path + (car candidate-dirs)))) + (if (file-readable-p buddy-file-path) + buddy-file-path + (erlang-eunit-locate-buddy orig-file-path (cdr candidate-dirs)))))) + +(defun erlang-eunit-buddy-file-path (orig-file-path buddy-dir-name) + (let* ((orig-dir-name (file-name-directory orig-file-path)) + (buddy-dir-name (file-truename + (filename-join orig-dir-name buddy-dir-name))) + (buddy-base-name (erlang-eunit-buddy-basename orig-file-path))) + (filename-join buddy-dir-name buddy-base-name))) + +;;; Return the basename of the buddy file: +;;; /tmp/foo/src/x.erl --> x_tests.erl +;;; /tmp/foo/test/x_tests.erl --> x.erl +(defun erlang-eunit-buddy-basename (file-path) + (let ((src-module-name (erlang-eunit-source-module-name file-path))) + (cond + ((erlang-eunit-src-file-p file-path) + (concat src-module-name "_tests.erl")) + ((erlang-eunit-test-file-p file-path) + (concat src-module-name ".erl"))))) + +;;; Checks whether a file is a source file or not +(defun erlang-eunit-src-file-p (file-path) + (not (erlang-eunit-test-file-p file-path))) ;;; Checks whether a file is a EUnit test file or not (defun erlang-eunit-test-file-p (file-path) @@ -93,23 +127,17 @@ buffer and vice versa" ;;; /tmp/foo/test/x_tests.erl --> x (defun erlang-eunit-source-module-name (file-path) (interactive) - (let* ((file-name (file-name-nondirectory file-path)) - (base-name (file-name-sans-extension file-name))) - (if (string-match "^\\(.+\\)_tests$" base-name) - (substring base-name (match-beginning 1) (match-end 1)) - base-name))) - -;;; Return the directory name which is common to both src and test -;;; /tmp/foo/src/x.erl --> /tmp/foo -;;; /tmp/foo/test/x_tests.erl --> /tmp/foo -(defun erlang-eunit-file-root-dir-name (file-path) - (erlang-eunit-dir-parent-dirname (file-name-directory file-path))) - -;;; Return the parent directory name of a directory -;;; /tmp/foo/ --> /tmp -;;; /tmp/foo --> /tmp -(defun erlang-eunit-dir-parent-dirname (dir-name) - (file-name-directory (directory-file-name dir-name))) + (let ((module-name (erlang-eunit-module-name file-path))) + (if (string-match "^\\(.+\\)_tests$" module-name) + (substring module-name (match-beginning 1) (match-end 1)) + module-name))) + +;;; Return the module name of the file +;;; /tmp/foo/src/x.erl --> x +;;; /tmp/foo/test/x_tests.erl --> x_tests +(defun erlang-eunit-module-name (file-path) + (interactive) + (file-name-sans-extension (file-name-nondirectory file-path))) ;;; Older emacsen don't have string-match-p. (defun erlang-eunit-string-match-p (regexp string &optional start) @@ -125,25 +153,158 @@ buffer and vice versa" (concat dir file) (concat dir "/" file))) +;;; Get info about the most recent running of EUnit +(defun erlang-eunit-recent (key) + (cdr (assq key erlang-eunit-recent-info))) + +;;; Record info about the most recent running of EUnit +;;; Known modes are 'module-mode and 'test-mode +(defun erlang-eunit-record-recent (mode module test) + (setcdr (assq 'mode erlang-eunit-recent-info) mode) + (setcdr (assq 'module erlang-eunit-recent-info) module) + (setcdr (assq 'test erlang-eunit-recent-info) test)) + +;;; Record whether the most recent running of EUnit included cover +;;; compilation +(defun erlang-eunit-record-recent-compile (under-cover) + (setcdr (assq 'cover erlang-eunit-recent-info) under-cover)) + +;;; Determine options for EUnit. +(defun erlang-eunit-opts () + (if current-prefix-arg ", [verbose]" "")) + +;;; Determine current test function +(defun erlang-eunit-current-test () + (save-excursion + (erlang-end-of-function 1) + (erlang-beginning-of-function 1) + (erlang-name-of-function))) + +(defun erlang-eunit-simple-test-p (test-name) + (if (erlang-eunit-string-match-p "^\\(.+\\)_test$" test-name) t nil)) + +(defun erlang-eunit-test-generator-p (test-name) + (if (erlang-eunit-string-match-p "^\\(.+\\)_test_$" test-name) t nil)) + +;;; Run one EUnit test +(defun erlang-eunit-run-test (module-name test-name) + (let ((command + (cond ((erlang-eunit-simple-test-p test-name) + (format "eunit:test({%s, %s}%s)." + module-name test-name (erlang-eunit-opts))) + ((erlang-eunit-test-generator-p test-name) + (format "eunit:test({generator, %s, %s}%s)." + module-name test-name (erlang-eunit-opts))) + (t (format "%% WARNING: '%s' is not a test function" test-name))))) + (erlang-eunit-record-recent 'test-mode module-name test-name) + (erlang-eunit-inferior-erlang-send-command command))) + ;;; Run EUnit tests for the current module -(defun erlang-eunit-run-tests () - "Run the EUnit test suite for the current module. +(defun erlang-eunit-run-module-tests (module-name) + (let ((command (format "eunit:test(%s%s)." module-name (erlang-eunit-opts)))) + (erlang-eunit-record-recent 'module-mode module-name nil) + (erlang-eunit-inferior-erlang-send-command command))) -With prefix arg, runs tests with the verbose flag set." +(defun erlang-eunit-compile-and-run-recent () + "Compile the source and test files and repeat the most recent EUnit test run. + +With prefix arg, compiles for debug and runs tests with the verbose flag set." (interactive) - (let* ((module-name (erlang-add-quotes-if-needed - (erlang-eunit-source-module-name buffer-file-name))) - (opts (if current-prefix-arg ", [verbose]" "")) - (command (format "eunit:test(%s%s)." module-name opts))) - (erlang-eunit-inferior-erlang-send-command command))) + (case (erlang-eunit-recent 'mode) + ('test-mode + (erlang-eunit-compile-and-test + 'erlang-eunit-run-test (list (erlang-eunit-recent 'module) + (erlang-eunit-recent 'test)))) + ('module-mode + (erlang-eunit-compile-and-test + 'erlang-eunit-run-module-tests (list (erlang-eunit-recent 'module)) + (erlang-eunit-recent 'cover))) + (t (error "EUnit has not yet been run. Please run a test first.")))) + +(defun erlang-eunit-cover-compile () + "Cover compile current module." + (interactive) + (let* ((erlang-compile-extra-opts + (append (list 'debug_info) erlang-compile-extra-opts)) + (module-name + (erlang-add-quotes-if-needed + (erlang-eunit-module-name buffer-file-name))) + (compile-command + (format "cover:compile_beam(%s)." module-name))) + (erlang-compile) + (if (erlang-eunit-last-compilation-successful-p) + (erlang-eunit-inferior-erlang-send-command compile-command)))) + +(defun erlang-eunit-analyze-coverage () + "Analyze the data collected by cover tool for the module in the +current buffer. + +Assumes that the module has been cover compiled prior to this +call. This function will do two things: print the number of +covered and uncovered functions in the erlang shell and display a +new buffer called *<module name> coverage* which shows the source +code along with the coverage analysis results." + (interactive) + (let* ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-module-name buffer-file-name))) + (tmp-filename (make-temp-file "cover")) + (analyze-command (format "cover:analyze_to_file(%s, \"%s\"). " + module-name tmp-filename)) + (buf-name (format "*%s coverage*" module-name))) + (erlang-eunit-inferior-erlang-send-command analyze-command) + ;; The purpose of the following snippet is to get the result of the + ;; analysis from a file into a new buffer (or an old, if one with + ;; the specified name already exists). Also we want the erlang-mode + ;; *and* view-mode to be enabled. + (save-excursion + (let ((buf (get-buffer-create (format "*%s coverage*" module-name)))) + (set-buffer buf) + (setq buffer-read-only nil) + (insert-file-contents tmp-filename nil nil nil t) + (if (= (buffer-size) 0) + (kill-buffer buf) + ;; FIXME: this would be a good place to enable (emacs-mode) + ;; to get some nice syntax highlighting in the + ;; coverage report, but it doesn't play well with + ;; flymake. Leave it off for now. + (view-buffer buf)))) + (delete-file tmp-filename))) + +(defun erlang-eunit-compile-and-run-current-test () + "Compile the source and test files and run the current EUnit test. -;;; Compile source and EUnit test file and finally run EUnit tests for -;;; the current module -(defun erlang-eunit-compile-and-run-tests () - "Compile the source and test files and run the EUnit test suite. +With prefix arg, compiles for debug and runs tests with the verbose flag set." + (interactive) + (let ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-module-name buffer-file-name))) + (test-name (erlang-eunit-current-test))) + (erlang-eunit-compile-and-test + 'erlang-eunit-run-test (list module-name test-name)))) + +(defun erlang-eunit-compile-and-run-module-tests () + "Compile the source and test files and run all EUnit tests in the module. With prefix arg, compiles for debug and runs tests with the verbose flag set." (interactive) + (let ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-source-module-name buffer-file-name)))) + (erlang-eunit-compile-and-test + 'erlang-eunit-run-module-tests (list module-name)))) + +;;; Compile source and EUnit test file and finally run EUnit tests for +;;; the current module +(defun erlang-eunit-compile-and-test (test-fun test-args &optional under-cover) + "Compile the source and test files and run the EUnit test suite. + +If under-cover is set to t, the module under test is compile for +code coverage analysis. If under-cover is left out or not set, +coverage analysis is disabled. The result of the code coverage +is both printed to the erlang shell (the number of covered vs +uncovered functions in a module) and written to a buffer called +*<module> coverage* (which shows the source code for the module +and the number of times each line is covered). +With prefix arg, compiles for debug and runs tests with the verbose flag set." + (erlang-eunit-record-recent-compile under-cover) (let ((src-filename (erlang-eunit-src-filename buffer-file-name)) (test-filename (erlang-eunit-test-filename buffer-file-name))) @@ -151,7 +312,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." ;; below, is to ask the question about saving buffers only once, ;; instead of possibly several: one for each file to compile, ;; for instance for both x.erl and x_tests.erl. - (save-some-buffers) + (save-some-buffers erlang-eunit-autosave) (flet ((save-some-buffers (&optional any) nil)) ;; Compilation of the source file is mandatory (the file must @@ -159,23 +320,56 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." ;; test file on the other hand, is optional, since eunit tests may ;; be placed in the source file instead. Any compilation error ;; will prevent the subsequent steps to be run (hence the `and') - (and (erlang-eunit-compile-file src-filename) + (and (erlang-eunit-compile-file src-filename under-cover) (if (file-readable-p test-filename) (erlang-eunit-compile-file test-filename) t) - (erlang-eunit-run-tests))))) + (apply test-fun test-args) + (if under-cover + (save-excursion + (set-buffer (find-file-noselect src-filename)) + (erlang-eunit-analyze-coverage))))))) + +(defun erlang-eunit-compile-and-run-module-tests-under-cover () + "Compile the source and test files and run the EUnit test suite and measure +code coverage. -(defun erlang-eunit-compile-file (file-path) +With prefix arg, compiles for debug and runs tests with the verbose flag set." + (interactive) + (let ((module-name (erlang-add-quotes-if-needed + (erlang-eunit-source-module-name buffer-file-name)))) + (erlang-eunit-compile-and-test + 'erlang-eunit-run-module-tests (list module-name) t))) + +(defun erlang-eunit-compile-file (file-path &optional under-cover) (if (file-readable-p file-path) (save-excursion - (set-buffer (find-file-noselect file-path)) - (erlang-compile) - (erlang-eunit-last-compilation-successful-p)) + (set-buffer (find-file-noselect file-path)) + ;; In order to run a code coverage analysis on a + ;; module, we have two options: + ;; + ;; * either compile the module with cover:compile instead of the + ;; regular compiler + ;; + ;; * or first compile the module with the regular compiler (but + ;; *with* debug_info) and then compile it for coverage + ;; analysis using cover:compile_beam. + ;; + ;; We could accomplish the first by changing the + ;; erlang-compile-erlang-function to cover:compile, but there's + ;; a risk that that's used for other purposes. Therefore, a + ;; safer alternative (although with more steps) is to add + ;; debug_info to the list of compiler options and go for the + ;; second alternative. + (if under-cover + (erlang-eunit-cover-compile) + (erlang-compile)) + (erlang-eunit-last-compilation-successful-p)) (let ((msg (format "Could not read %s" file-path))) - (erlang-eunit-inferior-erlang-send-command + (erlang-eunit-inferior-erlang-send-command (format "%% WARNING: %s" msg)) (error msg)))) - + (defun erlang-eunit-last-compilation-successful-p () (save-excursion (set-buffer inferior-erlang-buffer) @@ -184,7 +378,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (lambda (re) (let ((continue t) (result t)) (while continue ; ignore warnings, stop at errors - (if (re-search-forward re (point-max) t) + (if (re-search-forward re (point-max) t) (if (erlang-eunit-is-compilation-warning) t (setq result nil) @@ -195,7 +389,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (mapcar (lambda (e) (car e)) erlang-error-regexp-alist)))) (defun erlang-eunit-is-compilation-warning () - (erlang-eunit-string-match-p + (erlang-eunit-string-match-p "[0-9]+: Warning:" (buffer-substring (line-beginning-position) (line-end-position)))) @@ -221,22 +415,22 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." ;;; Key bindings ;;;==================================================================== -(defvar erlang-eunit-toggle-src-and-test-file-other-window-key "\C-c\C-et" - "*Key to which the `erlang-eunit-toggle-src-and-test-file-other-window' -function will be bound.") -(defvar erlang-eunit-compile-and-run-tests-key "\C-c\C-ek" - "*Key to which the `erlang-eunit-compile-and-run-tests' -function will be bound.") +(defconst erlang-eunit-key-bindings + '(("\C-c\C-et" erlang-eunit-toggle-src-and-test-file-other-window) + ("\C-c\C-ek" erlang-eunit-compile-and-run-module-tests) + ("\C-c\C-ej" erlang-eunit-compile-and-run-current-test) + ("\C-c\C-el" erlang-eunit-compile-and-run-recent) + ("\C-c\C-ec" erlang-eunit-compile-and-run-module-tests-under-cover) + ("\C-c\C-ev" erlang-eunit-cover-compile) + ("\C-c\C-ea" erlang-eunit-analyze-coverage))) (defun erlang-eunit-add-key-bindings () - (erlang-eunit-ensure-keymap-for-key - erlang-eunit-toggle-src-and-test-file-other-window-key) - (local-set-key erlang-eunit-toggle-src-and-test-file-other-window-key - 'erlang-eunit-toggle-src-and-test-file-other-window) - (erlang-eunit-ensure-keymap-for-key - erlang-eunit-compile-and-run-tests-key) - (local-set-key erlang-eunit-compile-and-run-tests-key - 'erlang-eunit-compile-and-run-tests)) + (dolist (binding erlang-eunit-key-bindings) + (erlang-eunit-bind-key (car binding) (cadr binding)))) + +(defun erlang-eunit-bind-key (key function) + (erlang-eunit-ensure-keymap-for-key key) + (local-set-key key function)) (defun erlang-eunit-ensure-keymap-for-key (key-seq) (let ((prefix-keys (butlast (append key-seq nil))) diff --git a/lib/tools/emacs/erlang-flymake.el b/lib/tools/emacs/erlang-flymake.el new file mode 100644 index 0000000000..bc368e9454 --- /dev/null +++ b/lib/tools/emacs/erlang-flymake.el @@ -0,0 +1,102 @@ +;; erlang-flymake.el +;; +;; Syntax check erlang source code on the fly (integrates with flymake). +;; +;; Start using flymake with erlang by putting the following somewhere +;; in your .emacs file: +;; +;; (require 'erlang-flymake) +;; +;; Flymake is rather eager and does its syntax checks frequently by +;; default and if you are bothered by this, you might want to put the +;; following in your .emacs as well: +;; +;; (erlang-flymake-only-on-save) +;; +;; There are a couple of variables which control the compilation options: +;; * erlang-flymake-get-code-path-dirs-function +;; * erlang-flymake-get-include-dirs-function +;; * erlang-flymake-extra-opts +;; +;; This code is inspired by http://www.emacswiki.org/emacs/FlymakeErlang. + +(require 'flymake) +(eval-when-compile + (require 'cl)) + +(defvar erlang-flymake-command + "erlc" + "The command that will be used to perform the syntax check") + +(defvar erlang-flymake-get-code-path-dirs-function + 'erlang-flymake-get-code-path-dirs + "Return a list of ebin directories to add to the code path.") + +(defvar erlang-flymake-get-include-dirs-function + 'erlang-flymake-get-include-dirs + "Return a list of include directories to add to the compiler options.") + +(defvar erlang-flymake-extra-opts + (list "+warn_obsolete_guard" + "+warn_unused_import" + "+warn_shadow_vars" + "+warn_export_vars" + "+strong_validation" + "+report") + "A list of options that will be passed to the compiler") + +(defun erlang-flymake-only-on-save () + "Trigger flymake only when the buffer is saved (disables syntax +check on newline and when there are no changes)." + (interactive) + ;; There doesn't seem to be a way of disabling this; set to the + ;; largest int available as a workaround (most-positive-fixnum + ;; equates to 8.5 years on my machine, so it ought to be enough ;-) ) + (setq flymake-no-changes-timeout most-positive-fixnum) + (setq flymake-start-syntax-check-on-newline nil)) + + +(defun erlang-flymake-get-code-path-dirs () + (list (concat (erlang-flymake-get-app-dir) "ebin"))) + +(defun erlang-flymake-get-include-dirs () + (list (concat (erlang-flymake-get-app-dir) "include"))) + +(defun erlang-flymake-get-app-dir () + (let ((src-path (file-name-directory (buffer-file-name)))) + (file-name-directory (directory-file-name src-path)))) + +(defun erlang-flymake-init () + (let* ((temp-file + (flet ((flymake-get-temp-dir () (erlang-flymake-temp-dir))) + (flymake-init-create-temp-buffer-copy + 'flymake-create-temp-with-folder-structure))) + (code-dir-opts + (erlang-flymake-flatten + (mapcar (lambda (dir) (list "-pa" dir)) + (funcall erlang-flymake-get-code-path-dirs-function)))) + (inc-dir-opts + (erlang-flymake-flatten + (mapcar (lambda (dir) (list "-I" dir)) + (funcall erlang-flymake-get-include-dirs-function)))) + (compile-opts + (append inc-dir-opts + code-dir-opts + erlang-flymake-extra-opts))) + (list erlang-flymake-command (append compile-opts (list temp-file))))) + +(defun erlang-flymake-temp-dir () + ;; Squeeze the user's name in there in order to make sure that files + ;; for two users who are working on the same computer (like a linux + ;; box) don't collide + (format "%s/flymake-%s" temporary-file-directory user-login-name)) + +(defun erlang-flymake-flatten (list) + (apply #'append list)) + +(add-to-list 'flymake-allowed-file-name-masks + '("\\.erl\\'" erlang-flymake-init)) +(add-hook 'erlang-mode-hook 'flymake-mode) + +(provide 'erlang-flymake) +;; erlang-flymake ends here diff --git a/lib/tools/emacs/erlang-start.el b/lib/tools/emacs/erlang-start.el index 542e81f24c..bbcea3e46a 100644 --- a/lib/tools/emacs/erlang-start.el +++ b/lib/tools/emacs/erlang-start.el @@ -90,6 +90,11 @@ (or (assoc (car b) auto-mode-alist) (setq auto-mode-alist (cons b auto-mode-alist)))) +;; +;; Associate files using interpreter "escript" with Erlang mode. +;; + +(add-to-list 'interpreter-mode-alist (cons "escript" 'erlang-mode)) ;; ;; Ignore files ending in ".jam", ".vee", and ".beam" when performing diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index da586ee09a..ed825a298f 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -659,24 +659,30 @@ resulting regexp is surrounded by \\_< and \\_>." (eval-and-compile (defconst erlang-guards-regexp (erlang-regexp-opt erlang-guards 'symbols))) - (eval-and-compile (defvar erlang-predefined-types '("any" "arity" + "boolean" "byte" "char" "cons" "deep_string" + "iolist" "maybe_improper_list" + "module" "mfa" "nil" + "neg_integer" "none" "non_neg_integer" "nonempty_list" "nonempty_improper_list" "nonempty_maybe_improper_list" + "no_return" + "pos_integer" "string" + "term" "timeout") "Erlang type specs types")) @@ -885,15 +891,54 @@ files written in other languages than Erlang.") If nil, the inferior shell replaces the window. This is the traditional behaviour.") -(defvar erlang-mode-map nil +(defconst inferior-erlang-use-cmm (boundp 'minor-mode-overriding-map-alist) + "Non-nil means use `compilation-minor-mode' in Erlang shell.") + +(defvar erlang-mode-map + (let ((map (make-sparse-keymap))) + (unless (boundp 'indent-line-function) + (define-key map "\t" 'erlang-indent-command)) + (define-key map ";" 'erlang-electric-semicolon) + (define-key map "," 'erlang-electric-comma) + (define-key map "<" 'erlang-electric-lt) + (define-key map ">" 'erlang-electric-gt) + (define-key map "\C-m" 'erlang-electric-newline) + (if (not (boundp 'delete-key-deletes-forward)) + (define-key map "\177" 'backward-delete-char-untabify) + (define-key map [(backspace)] 'backward-delete-char-untabify)) + ;;(unless (boundp 'fill-paragraph-function) + (define-key map "\M-q" 'erlang-fill-paragraph) + (unless (boundp 'beginning-of-defun-function) + (define-key map "\M-\C-a" 'erlang-beginning-of-function) + (define-key map "\M-\C-e" 'erlang-end-of-function) + (define-key map '(meta control h) 'erlang-mark-function)) ; Xemacs + (define-key map "\M-\t" 'erlang-complete-tag) + (define-key map "\C-c\M-\t" 'tempo-complete-tag) + (define-key map "\M-+" 'erlang-find-next-tag) + (define-key map "\C-c\M-a" 'erlang-beginning-of-clause) + (define-key map "\C-c\M-b" 'tempo-backward-mark) + (define-key map "\C-c\M-e" 'erlang-end-of-clause) + (define-key map "\C-c\M-f" 'tempo-forward-mark) + (define-key map "\C-c\M-h" 'erlang-mark-clause) + (define-key map "\C-c\C-c" 'comment-region) + (define-key map "\C-c\C-j" 'erlang-generate-new-clause) + (define-key map "\C-c\C-k" 'erlang-compile) + (define-key map "\C-c\C-l" 'erlang-compile-display) + (define-key map "\C-c\C-s" 'erlang-show-syntactic-information) + (define-key map "\C-c\C-q" 'erlang-indent-function) + (define-key map "\C-c\C-u" 'erlang-uncomment-region) + (define-key map "\C-c\C-y" 'erlang-clone-arguments) + (define-key map "\C-c\C-a" 'erlang-align-arrows) + (define-key map "\C-c\C-z" 'erlang-shell-display) + (unless inferior-erlang-use-cmm + (define-key map "\C-x`" 'erlang-next-error)) + map) "*Keymap used in Erlang mode.") (defvar erlang-mode-abbrev-table nil "Abbrev table in use in Erlang-mode buffers.") (defvar erlang-mode-syntax-table nil "Syntax table in use in Erlang-mode buffers.") -(defconst inferior-erlang-use-cmm (boundp 'minor-mode-overriding-map-alist) - "Non-nil means use `compilation-minor-mode' in Erlang shell.") (defvar erlang-skel-file "erlang-skels" @@ -988,7 +1033,7 @@ behaviour.") (list (concat "^\\(-" erlang-atom-regexp "\\)\\(\\s-\\|\\.\\|(\\)") 1 (if (boundp 'font-lock-preprocessor-face) 'font-lock-preprocessor-face - 'font-lock-function-name-face))) + 'font-lock-constant-face))) "Font lock keyword highlighting attributes.") (defvar erlang-font-lock-keywords-quotes @@ -1019,10 +1064,12 @@ are highlighted by syntactic analysis.") (list (list (concat "?\\s-*\\(" erlang-atom-regexp "\\|" erlang-variable-regexp "\\)") - 1 'font-lock-type-face) + 1 'font-lock-constant-face) (list (concat "^\\(-\\(?:define\\|ifn?def\\)\\)\\s-*(\\s-*\\(" erlang-atom-regexp "\\|" erlang-variable-regexp "\\)") - (list 1 'font-lock-preprocessor-face t) + (if (boundp 'font-lock-preprocessor-face) + (list 1 'font-lock-preprocessor-face t) + (list 1 'font-lock-constant-face t)) (list 3 'font-lock-type-face t t)) (list "^-e\\(lse\\|ndif\\)\\>" 0 'font-lock-preprocessor-face t)) "Font lock keyword highlighting macros. @@ -1245,7 +1292,7 @@ Other commands: (setq major-mode 'erlang-mode) (setq mode-name "Erlang") (erlang-syntax-table-init) - (erlang-keymap-init) + (use-local-map erlang-mode-map) (erlang-electric-init) (erlang-menu-init) (erlang-mode-variables) @@ -1300,53 +1347,6 @@ Other commands: (set-syntax-table erlang-mode-syntax-table)) -(defun erlang-keymap-init () - (if erlang-mode-map - nil - (setq erlang-mode-map (make-sparse-keymap)) - (erlang-mode-commands erlang-mode-map)) - (use-local-map erlang-mode-map)) - - -(defun erlang-mode-commands (map) - (unless (boundp 'indent-line-function) - (define-key map "\t" 'erlang-indent-command)) - (define-key map ";" 'erlang-electric-semicolon) - (define-key map "," 'erlang-electric-comma) - (define-key map "<" 'erlang-electric-lt) - (define-key map ">" 'erlang-electric-gt) - (define-key map "\C-m" 'erlang-electric-newline) - (if (not (boundp 'delete-key-deletes-forward)) - (define-key map "\177" 'backward-delete-char-untabify) - (define-key map [(backspace)] 'backward-delete-char-untabify)) - ;;(unless (boundp 'fill-paragraph-function) - (define-key map "\M-q" 'erlang-fill-paragraph) - (unless (boundp 'beginning-of-defun-function) - (define-key map "\M-\C-a" 'erlang-beginning-of-function) - (define-key map "\M-\C-e" 'erlang-end-of-function) - (define-key map '(meta control h) 'erlang-mark-function)) ; Xemacs - (define-key map "\M-\t" 'erlang-complete-tag) - (define-key map "\C-c\M-\t" 'tempo-complete-tag) - (define-key map "\M-+" 'erlang-find-next-tag) - (define-key map "\C-c\M-a" 'erlang-beginning-of-clause) - (define-key map "\C-c\M-b" 'tempo-backward-mark) - (define-key map "\C-c\M-e" 'erlang-end-of-clause) - (define-key map "\C-c\M-f" 'tempo-forward-mark) - (define-key map "\C-c\M-h" 'erlang-mark-clause) - (define-key map "\C-c\C-c" 'comment-region) - (define-key map "\C-c\C-j" 'erlang-generate-new-clause) - (define-key map "\C-c\C-k" 'erlang-compile) - (define-key map "\C-c\C-l" 'erlang-compile-display) - (define-key map "\C-c\C-s" 'erlang-show-syntactic-information) - (define-key map "\C-c\C-q" 'erlang-indent-function) - (define-key map "\C-c\C-u" 'erlang-uncomment-region) - (define-key map "\C-c\C-y" 'erlang-clone-arguments) - (define-key map "\C-c\C-a" 'erlang-align-arrows) - (define-key map "\C-c\C-z" 'erlang-shell-display) - (unless inferior-erlang-use-cmm - (define-key map "\C-x`" 'erlang-next-error))) - - (defun erlang-electric-init () ;; Set up electric character functions to work with ;; delsel/pending-del mode. Also, set up text properties for bit @@ -1400,7 +1400,7 @@ Other commands: (set (make-local-variable 'imenu-prev-index-position-function) 'erlang-beginning-of-function) (set (make-local-variable 'imenu-extract-index-name-function) - 'erlang-get-function-name) + 'erlang-get-function-name-and-arity) (set (make-local-variable 'tempo-match-finder) "[^-a-zA-Z0-9_]\\([-a-zA-Z0-9_]*\\)\\=") (set (make-local-variable 'beginning-of-defun-function) @@ -1481,7 +1481,23 @@ Other commands: erlang-font-lock-keywords-3 erlang-font-lock-keywords-4) nil nil ((?_ . "w")) erlang-beginning-of-clause - (font-lock-mark-block-function . erlang-mark-clause)))) + (font-lock-mark-block-function . erlang-mark-clause) + (font-lock-syntactic-keywords + ;; A dollar sign right before the double quote that ends a + ;; string is not a character escape. + ;; + ;; And a "string" has with a double quote not escaped by a + ;; dollar sign, any number of non-backslash non-newline + ;; characters or escaped backslashes, a dollar sign + ;; (otherwise we wouldn't care) and a double quote. This + ;; doesn't match multi-line strings, but this is probably + ;; the best we can get, since while font-locking we don't + ;; know whether matching started inside a string: limiting + ;; search to a single line keeps things sane. + . (("\\(?:^\\|[^$]\\)\"\\(?:[^\"\n]\\|\\\\\"\\)*\\(\\$\\)\"" 1 "w") + ;; And the dollar sign in $\" escapes two characters, not + ;; just one. + ("\\(\\$\\)\\\\\\\"" 1 "'")))))) @@ -2490,9 +2506,10 @@ Value is list (stack token-start token-type in-what)." ((looking-at "\\(of\\)[^_a-zA-Z0-9]") ;; Must handle separately, try X of -> catch (if (and stack (eq (car (car stack)) 'try)) - (let ((try-column (nth 2 (car stack)))) + (let ((try-column (nth 2 (car stack))) + (try-pos (nth 1 (car stack)))) (erlang-pop stack) - (erlang-push (list 'icr token try-column) stack)))) + (erlang-push (list 'icr try-pos try-column) stack)))) ((looking-at "\\(fun\\)[^_a-zA-Z0-9]") ;; Push a new layer if we are defining a `fun' @@ -2653,7 +2670,8 @@ Value is list (stack token-start token-type in-what)." (cond ((eq (car (car stack)) '\() (erlang-pop stack) (if (and (eq (car (car stack)) 'fun) - (eq (car (car (cdr stack))) '::)) + (or (eq (car (car (last stack))) 'spec) + (eq (car (car (cdr stack))) '::))) ;; -type() ;; Inside fun type def ') closes fun definition (erlang-pop stack))) ((eq (car (car stack)) 'icr) @@ -2752,7 +2770,7 @@ Return nil if inside string, t if in a comment." ;; ;; `after' should be indented to the same level as the ;; corresponding receive. - (cond ((looking-at "\\(after\\|catch\\|of\\)\\($\\|[^_a-zA-Z0-9]\\)") + (cond ((looking-at "\\(after\\|of\\)\\($\\|[^_a-zA-Z0-9]\\)") (nth 2 stack-top)) ((looking-at "when[^_a-zA-Z0-9]") ;; Handling one when part @@ -2771,7 +2789,7 @@ Return nil if inside string, t if in a comment." ((and (eq (car stack-top) '||) (looking-at "\\(]\\|>>\\)[^_a-zA-Z0-9]")) (nth 2 (car (cdr stack)))) ;; Real indentation, where operators create extra indentation etc. - ((memq (car stack-top) '(-> || begin try)) + ((memq (car stack-top) '(-> || try begin)) (if (looking-at "\\(of\\)[^_a-zA-Z0-9]") (nth 2 stack-top) (goto-char (nth 1 stack-top)) @@ -2800,19 +2818,24 @@ Return nil if inside string, t if in a comment." (erlang-caddr (car stack)) 0)) ((looking-at "catch\\($\\|[^_a-zA-Z0-9]\\)") - (if (or (eq (car stack-top) 'try) - (eq (car (car (cdr stack))) 'icr)) - (progn - (if (eq (car stack-top) '->) - (erlang-pop stack)) - (if stack - (erlang-caddr (car stack)) - 0)) - base)) ;; old catch + ;; Are we in a try + (let ((start (if (eq (car stack-top) '->) + (car (cdr stack)) + stack-top))) + (if (null start) nil + (goto-char (nth 1 start))) + (cond ((looking-at "try\\($\\|[^_a-zA-Z0-9]\\)") + (progn + (if (eq (car stack-top) '->) + (erlang-pop stack)) + (if stack + (erlang-caddr (car stack)) + 0))) + (t (erlang-indent-standard indent-point token base 'nil))))) ;; old catch (t (erlang-indent-standard indent-point token base 'nil) )))) - )) + )) ((eq (car stack-top) 'when) (goto-char (nth 1 stack-top)) (if (looking-at "when\\s *\\($\\|%\\)") @@ -2838,27 +2861,32 @@ Return nil if inside string, t if in a comment." (current-column))) ;; Type and Spec indentation ((eq (car stack-top) '::) - (cond ((null erlang-argument-indent) - ;; indent to next column. - (+ 2 (nth 2 stack-top))) - ((looking-at "::[^_a-zA-Z0-9]") - (nth 2 stack-top)) - (t - (let ((start-alternativ (if (looking-at "|") 2 0))) - (goto-char (nth 1 stack-top)) - (- (cond ((looking-at "::\\s *\\($\\|%\\)") - ;; Line ends with :: - (if (eq (car (car (last stack))) 'spec) + (if (looking-at "}") + ;; Closing record definition with types + ;; pop stack and recurse + (erlang-calculate-stack-indent indent-point + (cons (erlang-pop stack) (cdr state))) + (cond ((null erlang-argument-indent) + ;; indent to next column. + (+ 2 (nth 2 stack-top))) + ((looking-at "::[^_a-zA-Z0-9]") + (nth 2 stack-top)) + (t + (let ((start-alternativ (if (looking-at "|") 2 0))) + (goto-char (nth 1 stack-top)) + (- (cond ((looking-at "::\\s *\\($\\|%\\)") + ;; Line ends with :: + (if (eq (car (car (last stack))) 'spec) (+ (erlang-indent-find-preceding-expr 1) erlang-argument-indent) - (+ (erlang-indent-find-preceding-expr 2) - erlang-argument-indent))) - (t - ;; Indent to the same column as the first - ;; argument. - (goto-char (+ 2 (nth 1 stack-top))) - (skip-chars-forward " \t") - (current-column))) start-alternativ))))) + (+ (erlang-indent-find-preceding-expr 2) + erlang-argument-indent))) + (t + ;; Indent to the same column as the first + ;; argument. + (goto-char (+ 2 (nth 1 stack-top))) + (skip-chars-forward " \t") + (current-column))) start-alternativ)))))) ))) (defun erlang-indent-standard (indent-point token base inside-parenthesis) @@ -2874,8 +2902,8 @@ Return nil if inside string, t if in a comment." (+ base erlang-indent-level)) (t (goto-char indent-point) - (cond ((memq (following-char) '(?\( ?{)) - ;; Function application or record. + (cond ((memq (following-char) '(?\( )) + ;; Function application. (+ (erlang-indent-find-preceding-expr) erlang-argument-indent)) ;; Empty line, or end; treat it as the end of @@ -2930,10 +2958,16 @@ This assumes that the preceding expression is either simple (skip-chars-backward " \t") ;; Needed to match the colon in "'foo':'bar'". (if (not (memq (preceding-char) '(?# ?:))) - col - (backward-char 1) - (forward-sexp -1) - (current-column))))) + col + ;; Special hack to handle: (note line break) + ;; [#myrecord{ + ;; foo = foo}] + (or + (ignore-errors + (backward-char 1) + (forward-sexp -1) + (current-column)) + col))))) (defun erlang-indent-parenthesis (stack-position) (let ((previous (erlang-indent-find-preceding-expr))) @@ -3472,8 +3506,8 @@ Normally used in conjunction with `erlang-beginning-of-clause', e.g.: (erlang-get-function-arrow)))" (and (save-excursion - (re-search-forward "[^-:]*-\\|:" (point-max) t) - (erlang-buffer-substring (- (point) 1) (+ (point) 1))))) + (re-search-forward "->" (point-max) t) + (erlang-buffer-substring (- (point) 2) (+ (point) 1))))) (defun erlang-get-function-arity () "Return the number of arguments of function at point, or nil." @@ -3502,6 +3536,13 @@ Normally used in conjunction with `erlang-beginning-of-clause', e.g.: res) (error nil))))) +(defun erlang-get-function-name-and-arity () + "Return the name and arity of the function at point, or nil. +The return value is a string of the form \"foo/1\"." + (let ((name (erlang-get-function-name)) + (arity (erlang-get-function-arity))) + (and name arity (format "%s/%d" name arity)))) + (defun erlang-get-function-arguments () "Return arguments of current function, or nil." (if (not (looking-at (eval-when-compile @@ -3677,6 +3718,7 @@ non-whitespace characters following the point on the current line." (setq erlang-electric-newline-inhibit nil) (setq erlang-electric-newline-inhibit t) (undo-boundary) + (erlang-indent-line) (end-of-line) (newline) (if (condition-case nil @@ -4897,9 +4939,14 @@ a prompt. When nil, we will wait forever, or until \\[keyboard-quit].") (defvar inferior-erlang-buffer nil "Buffer of last invoked inferior Erlang, or nil.") +;; Enable uniquifying Erlang shell buffers based on directory name. +(eval-after-load "uniquify" + '(add-to-list 'uniquify-list-buffers-directory-modes 'erlang-shell-mode)) + ;;;###autoload -(defun inferior-erlang () +(defun inferior-erlang (&optional command) "Run an inferior Erlang. +With prefix command, prompt for command to start Erlang with. This is just like running Erlang in a normal shell, except that an Emacs buffer is used for input and output. @@ -4913,17 +4960,37 @@ Entry to this mode calls the functions in the variables The following commands imitate the usual Unix interrupt and editing control characters: \\{erlang-shell-mode-map}" - (interactive) + (interactive + (when current-prefix-arg + (list (if (fboundp 'read-shell-command) + ;; `read-shell-command' is a new function in Emacs 23. + (read-shell-command "Erlang command: ") + (read-string "Erlang command: "))))) (require 'comint) - (let ((opts inferior-erlang-machine-options)) - (cond ((eq inferior-erlang-shell-type 'oldshell) - (setq opts (cons "-oldshell" opts))) - ((eq inferior-erlang-shell-type 'newshell) - (setq opts (append '("-newshell" "-env" "TERM" "vt100") opts)))) - (setq inferior-erlang-buffer - (apply 'make-comint - inferior-erlang-process-name inferior-erlang-machine - nil opts))) + (let (cmd opts) + (if command + (setq cmd "sh" + opts (list "-c" command)) + (setq cmd inferior-erlang-machine + opts inferior-erlang-machine-options) + (cond ((eq inferior-erlang-shell-type 'oldshell) + (setq opts (cons "-oldshell" opts))) + ((eq inferior-erlang-shell-type 'newshell) + (setq opts (append '("-newshell" "-env" "TERM" "vt100") opts))))) + + ;; Using create-file-buffer and list-buffers-directory in this way + ;; makes uniquify give each buffer a unique name based on the + ;; directory. + (let ((fake-file-name (expand-file-name inferior-erlang-buffer-name default-directory))) + (setq inferior-erlang-buffer (create-file-buffer fake-file-name)) + (apply 'make-comint-in-buffer + inferior-erlang-process-name + inferior-erlang-buffer + cmd + nil opts) + (with-current-buffer inferior-erlang-buffer + (setq list-buffers-directory fake-file-name)))) + (setq inferior-erlang-process (get-buffer-process inferior-erlang-buffer)) (if (> 21 erlang-emacs-major-version) ; funcalls to avoid compiler warnings @@ -4936,10 +5003,6 @@ editing control characters: (if (and (not (eq system-type 'windows-nt)) (eq inferior-erlang-shell-type 'newshell)) (setq comint-process-echoes t)) - ;; `rename-buffer' takes only one argument in Emacs 18. - (condition-case nil - (rename-buffer inferior-erlang-buffer-name t) - (error (rename-buffer inferior-erlang-buffer-name))) (erlang-shell-mode)) diff --git a/lib/tools/emacs/test.erl.indented b/lib/tools/emacs/test.erl.indented index 1ccced9177..2948ccf1b5 100644 --- a/lib/tools/emacs/test.erl.indented +++ b/lib/tools/emacs/test.erl.indented @@ -1,20 +1,20 @@ %% -*- erlang -*- %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %%%------------------------------------------------------------------- @@ -93,11 +93,27 @@ -type t13() :: maybe_improper_list(integer(), t11()). -type t14() :: [erl_scan:foo() | %% Should be highlighted - non_neg_integer() | nonempty_list() | + term() | + bool() | + byte() | + char() | + non_neg_integer() | nonempty_list() | + pos_integer() | + neg_integer() | + number() | + list() | nonempty_improper_list() | nonempty_maybe_improper_list() | + maybe_improper_list() | string() | iolist() | byte() | + module() | + mfa() | + node() | + timeout() | + no_return() | %% Should not be highlighted nonempty_() | nonlist() | - erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + + -type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>, <<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>| <<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>| @@ -146,6 +162,8 @@ | {'del_member', name(), pid()}, #state{}) -> {'noreply', #state{}}. +-spec all(fun((T) -> boolean()), List :: [T]) -> + boolean() when is_subtype(T, term()). % (*) -spec get_closest_pid(term()) -> Return :: pid() @@ -170,6 +188,9 @@ f19 = 3 :: integer()|undefined, f5 = 3 :: undefined|integer()}). +-record(state, { + sequence_number = 1 :: integer() + }). highlighting(X) % Function definitions should be highlighted @@ -349,6 +370,14 @@ indent_basics(X, Y, Z) % AD added clause foo. + +indent_nested() -> + [ + {foo, 2, "string"}, + {bar, 3, "another string"} + ]. + + indent_icr(Z) -> % icr = if case receive %% If if Z >= 0 -> @@ -483,7 +512,9 @@ indent_try_catch() -> file:close(Xfile) end; indent_try_catch() -> - try foo(bar) of + try + foo(bar) + of X when true andalso kalle -> io:format(stdout, "Parsing file ~s, ", @@ -541,14 +572,57 @@ indent_catch() -> C = catch B + float(43.1), - case catch (X) of + case catch foo(X) of + A -> + B + end, + + case + catch foo(X) + of A -> B end, + + case + foo(X) + of + A -> + catch B, + X + end, + try sune of _ -> foo catch _:_ -> baf - end. + end, + + try + sune + of + _ -> + X = 5, + (catch foo(X)), + X + 10 + catch _:_ -> baf + end, + + try + (catch sune) + of + _ -> + catch foo() %% BUGBUG can't handle catch inside try without parentheses + catch _:_ -> + baf + end, + + try + (catch exit()) + catch + _ -> + catch baf() + end, + ok. indent_binary() -> X = lists:foldr(fun(M) -> @@ -578,3 +652,8 @@ indent_comprehensions() -> true = (X rem 2) >>, ok. + +%% This causes an error in earlier erlang-mode versions. +foo() -> + [#foo{ + foo = foo}]. diff --git a/lib/tools/emacs/test.erl.orig b/lib/tools/emacs/test.erl.orig index 9b4203120b..1221c5655e 100644 --- a/lib/tools/emacs/test.erl.orig +++ b/lib/tools/emacs/test.erl.orig @@ -1,20 +1,20 @@ %% -*- erlang -*- %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %%%------------------------------------------------------------------- @@ -93,11 +93,27 @@ -type t13() :: maybe_improper_list(integer(), t11()). -type t14() :: [erl_scan:foo() | %% Should be highlighted - non_neg_integer() | nonempty_list() | + term() | + bool() | + byte() | + char() | + non_neg_integer() | nonempty_list() | + pos_integer() | + neg_integer() | + number() | + list() | nonempty_improper_list() | nonempty_maybe_improper_list() | + maybe_improper_list() | string() | iolist() | byte() | + module() | + mfa() | + node() | + timeout() | + no_return() | %% Should not be highlighted nonempty_() | nonlist() | -erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. + + -type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>, <<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>| <<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>| @@ -146,6 +162,8 @@ t15(),t20(),t21(), t22(),t25()}. | {'del_member', name(), pid()}, #state{}) -> {'noreply', #state{}}. +-spec all(fun((T) -> boolean()), List :: [T]) -> + boolean() when is_subtype(T, term()). % (*) -spec get_closest_pid(term()) -> Return :: pid() @@ -170,6 +188,9 @@ f18 :: 1 | 2 | 'undefined', f19 = 3 :: integer()|undefined, f5 = 3 :: undefined|integer()}). +-record(state, { + sequence_number = 1 :: integer() + }). highlighting(X) % Function definitions should be highlighted @@ -349,6 +370,14 @@ indent_basics(X, Y, Z) % AD added clause foo. + +indent_nested() -> + [ + {foo, 2, "string"}, + {bar, 3, "another string"} + ]. + + indent_icr(Z) -> % icr = if case receive %% If if Z >= 0 -> @@ -483,7 +512,9 @@ indent_try_catch() -> file:close(Xfile) end; indent_try_catch() -> - try foo(bar) of + try + foo(bar) + of X when true andalso kalle -> io:format(stdout, "Parsing file ~s, ", @@ -541,14 +572,57 @@ indent_catch() -> C = catch B + float(43.1), - case catch (X) of + case catch foo(X) of A -> B end, + + case + catch foo(X) + of + A -> + B + end, + + case + foo(X) + of + A -> + catch B, + X + end, + try sune of - _ -> foo - catch _:_ -> baf - end. + _ -> foo + catch _:_ -> baf + end, + + try +sune + of + _ -> + X = 5, + (catch foo(X)), + X + 10 + catch _:_ -> baf + end, + + try + (catch sune) + of + _ -> + catch foo() %% BUGBUG can't handle catch inside try without parentheses + catch _:_ -> + baf + end, + + try +(catch exit()) + catch +_ -> + catch baf() + end, + ok. indent_binary() -> X = lists:foldr(fun(M) -> @@ -578,3 +652,8 @@ Binary2 = << <<X:8>> || <<X:32,_:32>> <= <<0:512>>, true = (X rem 2) >>, ok. + +%% This causes an error in earlier erlang-mode versions. +foo() -> +[#foo{ +foo = foo}]. diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 1a7ebdc69a..ada2db45be 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(cover). @@ -35,23 +35,37 @@ %% remote_process_loop/1. %% %% TABLES -%% Each nodes has an ets table named 'cover_internal_data_table' -%% (?COVER_TABLE). This table contains the coverage data and is -%% continously updated when cover compiled code is executed. +%% Each nodes has two tables: cover_internal_data_table (?COVER_TABLE) and. +%% cover_internal_clause_table (?COVER_CLAUSE_TABLE). +%% ?COVER_TABLE contains the bump data i.e. the data about which lines +%% have been executed how many times. +%% ?COVER_CLAUSE_TABLE contains information about which clauses in which modules +%% cover is currently collecting statistics. %% -%% The main node owns a table named -%% 'cover_collected_remote_data_table' (?COLLECTION_TABLE). This table -%% contains data which is collected from remote nodes (either when a -%% remote node is stopped with cover:stop/1 or when analysing. When -%% analysing, data is even moved from the ?COVER_TABLE on the main -%% node to the ?COLLECTION_TABLE. +%% The main node owns tables named +%% 'cover_collected_remote_data_table' (?COLLECTION_TABLE) and +%% 'cover_collected_remote_clause_table' (?COLLECTION_CLAUSE_TABLE). +%% These tables contain data which is collected from remote nodes (either when a +%% remote node is stopped with cover:stop/1 or when analysing). When +%% analysing, data is even moved from the COVER tables on the main +%% node to the COLLECTION tables. %% %% The main node also has a table named 'cover_binary_code_table' %% (?BINARY_TABLE). This table contains the binary code for each cover %% compiled module. This is necessary so that the code can be loaded %% on remote nodes that are started after the compilation. %% - +%% PARELLALISM +%% To take advantage of SMP when doing the cover analysis both the data +%% collection and analysis has been parallelized. One process is spawned for +%% each node when collecting data, and on the remote node when collecting data +%% one process is spawned per module. +%% +%% When analyzing data it is possible to issue multiple analyse(_to_file)/X +%% calls at once. They are however all calls (for backwardscompatability +%% reasons) so the user of cover will have to spawn several processes to to the +%% calls ( or use async_analyse_to_file ). +%% %% External exports -export([start/0, start/1, @@ -61,6 +75,9 @@ analyse/1, analyse/2, analyse/3, analyze/1, analyze/2, analyze/3, analyse_to_file/1, analyse_to_file/2, analyse_to_file/3, analyze_to_file/1, analyze_to_file/2, analyze_to_file/3, + async_analyse_to_file/1,async_analyse_to_file/2, + async_analyse_to_file/3, async_analyze_to_file/1, + async_analyze_to_file/2, async_analyze_to_file/3, export/1, export/2, import/1, modules/0, imported/0, imported_modules/0, which_nodes/0, is_compiled/1, reset/1, reset/0, @@ -100,8 +117,10 @@ }). -define(COVER_TABLE, 'cover_internal_data_table'). +-define(COVER_CLAUSE_TABLE, 'cover_internal_clause_table'). -define(BINARY_TABLE, 'cover_binary_code_table'). -define(COLLECTION_TABLE, 'cover_collected_remote_data_table'). +-define(COLLECTION_CLAUSE_TABLE, 'cover_collected_remote_clause_table'). -define(TAG, cover_compiled). -define(SERVER, cover_server). @@ -114,6 +133,8 @@ true -> ?BLOCK(Expr) end). +-define(SPAWN_DBG(Tag,Value),put(Tag,Value)). + -include_lib("stdlib/include/ms_transform.hrl"). %%%---------------------------------------------------------------------- @@ -127,7 +148,10 @@ start() -> case whereis(?SERVER) of undefined -> Starter = self(), - Pid = spawn(fun() -> init_main(Starter) end), + Pid = spawn(fun() -> + ?SPAWN_DBG(start,[]), + init_main(Starter) + end), Ref = erlang:monitor(process,Pid), Return = receive @@ -382,6 +406,30 @@ analyze_to_file(Module, OptOrOut) -> analyse_to_file(Module, OptOrOut). analyze_to_file(Module, OutFile, Options) -> analyse_to_file(Module, OutFile, Options). +async_analyse_to_file(Module) -> + do_spawn(?MODULE, analyse_to_file, [Module]). +async_analyse_to_file(Module, OutFileOrOpts) -> + do_spawn(?MODULE, analyse_to_file, [Module, OutFileOrOpts]). +async_analyse_to_file(Module, OutFile, Options) -> + do_spawn(?MODULE, analyse_to_file, [Module, OutFile, Options]). + +do_spawn(M,F,A) -> + spawn_link(fun() -> + case apply(M,F,A) of + {ok, _} -> + ok; + {error, Reason} -> + exit(Reason) + end + end). + +async_analyze_to_file(Module) -> + async_analyse_to_file(Module). +async_analyze_to_file(Module, OutFileOrOpts) -> + async_analyse_to_file(Module, OutFileOrOpts). +async_analyze_to_file(Module, OutFile, Options) -> + async_analyse_to_file(Module, OutFile, Options). + outfilename(Module,Opts) -> case lists:member(html,Opts) of true -> @@ -500,6 +548,8 @@ remote_call(Node,Request) -> Return end. +remote_reply(Proc,Reply) when is_pid(Proc) -> + Proc ! {?SERVER,Reply}; remote_reply(MainNode,Reply) -> {?SERVER,MainNode} ! {?SERVER,Reply}. @@ -509,9 +559,15 @@ remote_reply(MainNode,Reply) -> init_main(Starter) -> register(?SERVER,self()), - ets:new(?COVER_TABLE, [set, public, named_table]), + %% Having write concurrancy here gives a 40% performance boost + %% when collect/1 is called. + ets:new(?COVER_TABLE, [set, public, named_table + ,{write_concurrency, true} + ]), + ets:new(?COVER_CLAUSE_TABLE, [set, public, named_table]), ets:new(?BINARY_TABLE, [set, named_table]), ets:new(?COLLECTION_TABLE, [set, public, named_table]), + ets:new(?COLLECTION_CLAUSE_TABLE, [set, public, named_table]), process_flag(trap_exit,true), Starter ! {?SERVER,started}, main_process_loop(#main_state{}). @@ -593,40 +649,10 @@ main_process_loop(State) -> end; {From, {export,OutFile,Module}} -> - case file:open(OutFile,[write,binary,raw]) of - {ok,Fd} -> - Reply = - case Module of - '_' -> - export_info(State#main_state.imported), - collect(State#main_state.nodes), - do_export_table(State#main_state.compiled, - State#main_state.imported, - Fd); - _ -> - export_info(Module,State#main_state.imported), - case is_loaded(Module, State) of - {loaded, File} -> - [{Module,Clauses}] = - ets:lookup(?COVER_TABLE,Module), - collect(Module, Clauses, - State#main_state.nodes), - do_export_table([{Module,File}],[],Fd); - {imported, File, ImportFiles} -> - %% don't know if I should allow this - - %% export a module which is only imported - Imported = [{Module,File,ImportFiles}], - do_export_table([],Imported,Fd); - _NotLoaded -> - {error,{not_cover_compiled,Module}} - end - end, - file:close(Fd), - reply(From, Reply); - {error,Reason} -> - reply(From, {error, {cant_open_file,OutFile,Reason}}) - - end, + spawn(fun() -> + ?SPAWN_DBG(export,{OutFile, Module}), + do_export(Module, OutFile, From, State) + end), main_process_loop(State); {From, {import,File}} -> @@ -689,109 +715,76 @@ main_process_loop(State) -> end, State#main_state.nodes), reload_originals(State#main_state.compiled), + unregister(?SERVER), reply(From, ok); - {From, {Request, Module}} -> - case is_loaded(Module, State) of - {loaded, File} -> - {Reply,State1} = - case Request of - {analyse, Analysis, Level} -> - analyse_info(Module,State#main_state.imported), - [{Module,Clauses}] = - ets:lookup(?COVER_TABLE,Module), - collect(Module,Clauses,State#main_state.nodes), - R = do_analyse(Module, Analysis, Level, Clauses), - {R,State}; - - {analyse_to_file, OutFile, Opts} -> - R = case find_source(File) of - {beam,_BeamFile} -> - {error,no_source_code_found}; - ErlFile -> - Imported = State#main_state.imported, - analyse_info(Module,Imported), - [{Module,Clauses}] = - ets:lookup(?COVER_TABLE,Module), - collect(Module, Clauses, - State#main_state.nodes), - HTML = lists:member(html,Opts), - do_analyse_to_file(Module,OutFile, - ErlFile,HTML) - end, - {R,State}; - - is_compiled -> - {{file, File},State}; - - reset -> - R = do_reset_main_node(Module, - State#main_state.nodes), - Imported = - remove_imported(Module, - State#main_state.imported), - {R,State#main_state{imported=Imported}} - end, - reply(From, Reply), - main_process_loop(State1); - - {imported,File,_ImportFiles} -> - {Reply,State1} = - case Request of - {analyse, Analysis, Level} -> - analyse_info(Module,State#main_state.imported), - [{Module,Clauses}] = - ets:lookup(?COLLECTION_TABLE,Module), - R = do_analyse(Module, Analysis, Level, Clauses), - {R,State}; - - {analyse_to_file, OutFile, Opts} -> - R = case find_source(File) of - {beam,_BeamFile} -> - {error,no_source_code_found}; - ErlFile -> - Imported = State#main_state.imported, - analyse_info(Module,Imported), - HTML = lists:member(html,Opts), - do_analyse_to_file(Module,OutFile, - ErlFile,HTML) - end, - {R,State}; - - is_compiled -> - {false,State}; - - reset -> - R = do_reset_collection_table(Module), - Imported = - remove_imported(Module, - State#main_state.imported), - {R,State#main_state{imported=Imported}} - end, - reply(From, Reply), - main_process_loop(State1); - - NotLoaded -> - Reply = - case Request of - is_compiled -> - false; - _ -> - {error, {not_cover_compiled,Module}} - end, - Compiled = - case NotLoaded of - unloaded -> - do_clear(Module), - remote_unload(State#main_state.nodes,[Module]), - update_compiled([Module], - State#main_state.compiled); - false -> - State#main_state.compiled + {From, {{analyse, Analysis, Level}, Module}} -> + S = try + Loaded = is_loaded(Module, State), + spawn(fun() -> + ?SPAWN_DBG(analyse,{Module,Analysis, Level}), + do_parallel_analysis( + Module, Analysis, Level, + Loaded, From, State) + end), + State + catch throw:Reason -> + reply(From,{error, {not_cover_compiled,Module}}), + not_loaded(Module, Reason, State) + end, + main_process_loop(S); + + {From, {{analyse_to_file, OutFile, Opts},Module}} -> + S = try + Loaded = is_loaded(Module, State), + spawn(fun() -> + ?SPAWN_DBG(analyse_to_file, + {Module,OutFile, Opts}), + do_parallel_analysis_to_file( + Module, OutFile, Opts, + Loaded, From, State) + end), + State + catch throw:Reason -> + reply(From,{error, {not_cover_compiled,Module}}), + not_loaded(Module, Reason, State) + end, + main_process_loop(S); + + {From, {is_compiled, Module}} -> + S = try is_loaded(Module, State) of + {loaded, File} -> + reply(From,{file, File}), + State; + {imported,_File,_ImportFiles} -> + reply(From,false), + State + catch throw:Reason -> + reply(From,false), + not_loaded(Module, Reason, State) + end, + main_process_loop(S); + + {From, {reset, Module}} -> + S = try + Loaded = is_loaded(Module,State), + R = case Loaded of + {loaded, _File} -> + do_reset_main_node( + Module, State#main_state.nodes); + {imported, _File, _} -> + do_reset_collection_table(Module) end, - reply(From, Reply), - main_process_loop(State#main_state{compiled=Compiled}) - end; + Imported = + remove_imported(Module, + State#main_state.imported), + reply(From, R), + State#main_state{imported=Imported} + catch throw:Reason -> + reply(From,{error, {not_cover_compiled,Module}}), + not_loaded(Module, Reason, State) + end, + main_process_loop(S); {'EXIT',Pid,_Reason} -> %% Exit is trapped on the main node only, so this will only happen @@ -806,17 +799,17 @@ main_process_loop(State) -> main_process_loop(State) end. - - - - %%%---------------------------------------------------------------------- %%% cover_server on remote node %%%---------------------------------------------------------------------- init_remote(Starter,MainNode) -> register(?SERVER,self()), - ets:new(?COVER_TABLE, [set, public, named_table]), + ets:new(?COVER_TABLE, [set, public, named_table + %% write_concurrency here makes otp_8270 break :( + %,{write_concurrency, true} + ]), + ets:new(?COVER_CLAUSE_TABLE, [set, public, named_table]), Starter ! {self(),started}, remote_process_loop(#remote_state{main_node=MainNode}). @@ -842,33 +835,19 @@ remote_process_loop(State) -> remote_process_loop(State); {remote,collect,Module,CollectorPid} -> - MS = - case Module of - '_' -> ets:fun2ms(fun({M,C}) when is_atom(M) -> C end); - _ -> ets:fun2ms(fun({M,C}) when M=:=Module -> C end) - end, - AllClauses = lists:flatten(ets:select(?COVER_TABLE,MS)), - - %% Sending clause by clause in order to avoid large lists - lists:foreach( - fun({M,F,A,C,_L}) -> - Pattern = - {#bump{module=M, function=F, arity=A, clause=C}, '_'}, - Bumps = ets:match_object(?COVER_TABLE, Pattern), - %% Reset - lists:foreach(fun({Bump,_N}) -> - ets:insert(?COVER_TABLE, {Bump,0}) - end, - Bumps), - CollectorPid ! {chunk,Bumps} - end, - AllClauses), - CollectorPid ! done, - remote_reply(State#remote_state.main_node, ok), + self() ! {remote,collect,Module,CollectorPid, ?SERVER}; + + {remote,collect,Module,CollectorPid,From} -> + spawn(fun() -> + ?SPAWN_DBG(remote_collect, + {Module, CollectorPid, From}), + do_collect(Module, CollectorPid, From) + end), remote_process_loop(State); {remote,stop} -> reload_originals(State#remote_state.compiled), + unregister(?SERVER), remote_reply(State#remote_state.main_node, ok); get_status -> @@ -892,6 +871,33 @@ remote_process_loop(State) -> end. +do_collect(Module, CollectorPid, From) -> + AllMods = + case Module of + '_' -> ets:tab2list(?COVER_CLAUSE_TABLE); + _ -> ets:lookup(?COVER_CLAUSE_TABLE, Module) + end, + + %% Sending clause by clause in order to avoid large lists + pmap( + fun({_Mod,Clauses}) -> + lists:map(fun(Clause) -> + send_collected_data(Clause, CollectorPid) + end,Clauses) + end,AllMods), + CollectorPid ! done, + remote_reply(From, ok). + +send_collected_data({M,F,A,C,_L}, CollectorPid) -> + Pattern = + {#bump{module=M, function=F, arity=A, clause=C}, '_'}, + Bumps = ets:match_object(?COVER_TABLE, Pattern), + %% Reset + lists:foreach(fun({Bump,_N}) -> + ets:insert(?COVER_TABLE, {Bump,0}) + end, + Bumps), + CollectorPid ! {chunk,Bumps}. reload_originals([{Module,_File}|Compiled]) -> do_reload_original(Module), @@ -930,6 +936,9 @@ load_compiled([{Module,File,Binary,InitialTable}|Compiled],Acc) -> load_compiled([],Acc) -> Acc. +insert_initial_data([Item|Items]) when is_atom(element(1,Item)) -> + ets:insert(?COVER_CLAUSE_TABLE, Item), + insert_initial_data(Items); insert_initial_data([Item|Items]) -> ets:insert(?COVER_TABLE, Item), insert_initial_data(Items); @@ -955,7 +964,10 @@ remote_start(MainNode) -> case whereis(?SERVER) of undefined -> Starter = self(), - Pid = spawn(fun() -> init_remote(Starter,MainNode) end), + Pid = spawn(fun() -> + ?SPAWN_DBG(remote_start,{MainNode}), + init_remote(Starter,MainNode) + end), Ref = erlang:monitor(process,Pid), Return = receive @@ -970,14 +982,25 @@ remote_start(MainNode) -> {error,{already_started,Pid}} end. -%% Load a set of cover compiled modules on remote nodes -remote_load_compiled(Nodes,Compiled0) -> - Compiled = lists:map(fun get_data_for_remote_loading/1,Compiled0), +%% Load a set of cover compiled modules on remote nodes, +%% We do it ?MAX_MODS modules at a time so that we don't +%% run out of memory on the cover_server node. +-define(MAX_MODS, 10). +remote_load_compiled(Nodes,Compiled) -> + remote_load_compiled(Nodes, Compiled, [], 0). +remote_load_compiled(_Nodes, [], [], _ModNum) -> + ok; +remote_load_compiled(Nodes, Compiled, Acc, ModNum) + when Compiled == []; ModNum == ?MAX_MODS -> lists:foreach( fun(Node) -> - remote_call(Node,{remote,load_compiled,Compiled}) + remote_call(Node,{remote,load_compiled,Acc}) end, - Nodes). + Nodes), + remote_load_compiled(Nodes, Compiled, [], 0); +remote_load_compiled(Nodes, [MF | Rest], Acc, ModNum) -> + remote_load_compiled( + Nodes, Rest, [get_data_for_remote_loading(MF) | Acc], ModNum + 1). %% Read all data needed for loading a cover compiled module on a remote node %% Binary is the beam code for the module and InitialTable is the initial @@ -985,15 +1008,15 @@ remote_load_compiled(Nodes,Compiled0) -> get_data_for_remote_loading({Module,File}) -> [{Module,Binary}] = ets:lookup(?BINARY_TABLE,Module), %%! The InitialTable list will be long if the module is big - what to do?? - InitialTable = ets:select(?COVER_TABLE,ms(Module)), - {Module,File,Binary,InitialTable}. + InitialBumps = ets:select(?COVER_TABLE,ms(Module)), + InitialClauses = ets:lookup(?COVER_CLAUSE_TABLE,Module), + + {Module,File,Binary,InitialBumps ++ InitialClauses}. %% Create a match spec which returns the clause info {Module,InitInfo} and %% all #bump keys for the given module with 0 number of calls. ms(Module) -> - ets:fun2ms(fun({Module,InitInfo}) -> - {Module,InitInfo}; - ({Key,_}) when is_record(Key,bump),Key#bump.module=:=Module -> + ets:fun2ms(fun({Key,_}) when Key#bump.module=:=Module -> {Key,0} end). @@ -1015,27 +1038,30 @@ remote_reset(Module,Nodes) -> %% Collect data from remote nodes - used for analyse or stop(Node) remote_collect(Module,Nodes,Stop) -> - CollectorPid = spawn(fun() -> collector_proc(length(Nodes)) end), - lists:foreach( - fun(Node) -> - remote_call(Node,{remote,collect,Module,CollectorPid}), - if Stop -> remote_call(Node,{remote,stop}); - true -> ok - end - end, - Nodes). + pmap(fun(Node) -> + ?SPAWN_DBG(remote_collect, + {Module, Nodes, Stop}), + do_collection(Node, Module, Stop) + end, + Nodes). + +do_collection(Node, Module, Stop) -> + CollectorPid = spawn(fun collector_proc/0), + remote_call(Node,{remote,collect,Module,CollectorPid, self()}), + if Stop -> remote_call(Node,{remote,stop}); + true -> ok + end. %% Process which receives chunks of data from remote nodes - either when %% analysing or when stopping cover on the remote nodes. -collector_proc(0) -> - ok; -collector_proc(N) -> +collector_proc() -> + ?SPAWN_DBG(collector_proc, []), receive {chunk,Chunk} -> insert_in_collection_table(Chunk), - collector_proc(N); + collector_proc(); done -> - collector_proc(N-1) + ok end. insert_in_collection_table([{Key,Val}|Chunk]) -> @@ -1050,7 +1076,13 @@ insert_in_collection_table(Key,Val) -> ets:update_counter(?COLLECTION_TABLE, Key,Val); false -> - ets:insert(?COLLECTION_TABLE,{Key,Val}) + %% Make sure that there are no race conditions from ets:member + case ets:insert_new(?COLLECTION_TABLE,{Key,Val}) of + false -> + insert_in_collection_table(Key,Val); + _ -> + ok + end end. @@ -1071,14 +1103,15 @@ analyse_info(Module,Imported) -> export_info(_Module,[]) -> ok; -export_info(Module,Imported) -> - imported_info("Export",Module,Imported). +export_info(_Module,_Imported) -> + %% Do not print that the export includes imported modules + ok. export_info([]) -> ok; -export_info(Imported) -> - AllImportFiles = get_all_importfiles(Imported,[]), - io:format("Export includes data from imported files\n~p\n",[AllImportFiles]). +export_info(_Imported) -> + %% Do not print that the export includes imported modules + ok. get_all_importfiles([{_M,_F,ImportFiles}|Imported],Acc) -> NewAcc = do_get_all_importfiles(ImportFiles,Acc), @@ -1151,14 +1184,14 @@ is_loaded(Module, State) -> {ok, File} -> case code:which(Module) of ?TAG -> {loaded, File}; - _ -> unloaded + _ -> throw(unloaded) end; false -> case get_file(Module,State#main_state.imported) of {ok,File,ImportFiles} -> {imported, File, ImportFiles}; false -> - false + throw(not_loaded) end end. @@ -1257,7 +1290,7 @@ do_compile_beam(Module,Beam) -> %% Store info about all function clauses in database InitInfo = reverse(Vars#vars.init_info), - ets:insert(?COVER_TABLE, {Module, InitInfo}), + ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}), %% Store binary code so it can be loaded on remote nodes ets:insert(?BINARY_TABLE, {Module, Binary}), @@ -1791,9 +1824,8 @@ common_elems(L1, L2) -> %% Collect data for all modules collect(Nodes) -> %% local node - MS = ets:fun2ms(fun({M,C}) when is_atom(M) -> {M,C} end), - AllClauses = ets:select(?COVER_TABLE,MS), - move_modules(AllClauses), + AllClauses = ets:tab2list(?COVER_CLAUSE_TABLE), + pmap(fun move_modules/1,AllClauses), %% remote nodes remote_collect('_',Nodes,false). @@ -1801,7 +1833,7 @@ collect(Nodes) -> %% Collect data for one module collect(Module,Clauses,Nodes) -> %% local node - move_modules([{Module,Clauses}]), + move_modules({Module,Clauses}), %% remote nodes remote_collect(Module,Nodes,false). @@ -1809,12 +1841,9 @@ collect(Module,Clauses,Nodes) -> %% When analysing, the data from the local ?COVER_TABLE is moved to the %% ?COLLECTION_TABLE. Resetting data in ?COVER_TABLE -move_modules([{Module,Clauses}|AllClauses]) -> - ets:insert(?COLLECTION_TABLE,{Module,Clauses}), - move_clauses(Clauses), - move_modules(AllClauses); -move_modules([]) -> - ok. +move_modules({Module,Clauses}) -> + ets:insert(?COLLECTION_CLAUSE_TABLE,{Module,Clauses}), + move_clauses(Clauses). move_clauses([{M,F,A,C,_L}|Clauses]) -> Pattern = {#bump{module=M, function=F, arity=A, clause=C}, '_'}, @@ -1853,6 +1882,22 @@ find_source(File0) -> end end. +do_parallel_analysis(Module, Analysis, Level, Loaded, From, State) -> + analyse_info(Module,State#main_state.imported), + C = case Loaded of + {loaded, _File} -> + [{Module,Clauses}] = + ets:lookup(?COVER_CLAUSE_TABLE,Module), + collect(Module,Clauses,State#main_state.nodes), + Clauses; + _ -> + [{Module,Clauses}] = + ets:lookup(?COLLECTION_CLAUSE_TABLE,Module), + Clauses + end, + R = do_analyse(Module, Analysis, Level, C), + reply(From, R). + %% do_analyse(Module, Analysis, Level, Clauses)-> {ok,Answer} | {error,Error} %% Clauses = [{Module,Function,Arity,Clause,Lines}] do_analyse(Module, Analysis, line, _Clauses) -> @@ -1929,6 +1974,28 @@ merge_functions([{_MFA,R}|Functions], MFun, Result) -> merge_functions([], _MFun, Result) -> Result. +do_parallel_analysis_to_file(Module, OutFile, Opts, Loaded, From, State) -> + File = case Loaded of + {loaded, File0} -> + [{Module,Clauses}] = + ets:lookup(?COVER_CLAUSE_TABLE,Module), + collect(Module, Clauses, + State#main_state.nodes), + File0; + {imported, File0, _} -> + File0 + end, + case find_source(File) of + {beam,_BeamFile} -> + reply(From, {error,no_source_code_found}); + ErlFile -> + analyse_info(Module,State#main_state.imported), + HTML = lists:member(html,Opts), + R = do_analyse_to_file(Module,OutFile, + ErlFile,HTML), + reply(From, R) + end. + %% do_analyse_to_file(Module,OutFile,ErlFile) -> {ok,OutFile} | {error,Error} %% Module = atom() %% OutFile = ErlFile = string() @@ -2025,6 +2092,42 @@ fill2() -> ".| ". fill3() -> "| ". %%%--Export-------------------------------------------------------------- +do_export(Module, OutFile, From, State) -> + case file:open(OutFile,[write,binary,raw]) of + {ok,Fd} -> + Reply = + case Module of + '_' -> + export_info(State#main_state.imported), + collect(State#main_state.nodes), + do_export_table(State#main_state.compiled, + State#main_state.imported, + Fd); + _ -> + export_info(Module,State#main_state.imported), + try is_loaded(Module, State) of + {loaded, File} -> + [{Module,Clauses}] = + ets:lookup(?COVER_CLAUSE_TABLE,Module), + collect(Module, Clauses, + State#main_state.nodes), + do_export_table([{Module,File}],[],Fd); + {imported, File, ImportFiles} -> + %% don't know if I should allow this - + %% export a module which is only imported + Imported = [{Module,File,ImportFiles}], + do_export_table([],Imported,Fd) + catch throw:_ -> + {error,{not_cover_compiled,Module}} + end + end, + file:close(Fd), + reply(From, Reply); + {error,Reason} -> + reply(From, {error, {cant_open_file,OutFile,Reason}}) + + end. + do_export_table(Compiled, Imported, Fd) -> ModList = merge(Imported,Compiled), write_module_data(ModList,Fd). @@ -2041,7 +2144,7 @@ merge([],ModuleList) -> write_module_data([{Module,File}|ModList],Fd) -> write({file,Module,File},Fd), - [Clauses] = ets:lookup(?COLLECTION_TABLE,Module), + [Clauses] = ets:lookup(?COLLECTION_CLAUSE_TABLE,Module), write(Clauses,Fd), ModuleData = ets:match_object(?COLLECTION_TABLE,{#bump{module=Module},'_'}), do_write_module_data(ModuleData,Fd), @@ -2091,7 +2194,7 @@ do_import_to_table(Fd,ImportFile,Imported,DontImport) -> {Module,Clauses} -> case lists:member(Module,DontImport) of false -> - ets:insert(?COLLECTION_TABLE,{Module,Clauses}); + ets:insert(?COLLECTION_CLAUSE_TABLE,{Module,Clauses}); true -> ok end, @@ -2125,14 +2228,14 @@ do_reset_main_node(Module,Nodes) -> remote_reset(Module,Nodes). do_reset_collection_table(Module) -> - ets:delete(?COLLECTION_TABLE,Module), + ets:delete(?COLLECTION_CLAUSE_TABLE,Module), ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}). %% do_reset(Module) -> ok %% The reset is done on a per-clause basis to avoid building %% long lists in the case of very large modules do_reset(Module) -> - [{Module,Clauses}] = ets:lookup(?COVER_TABLE, Module), + [{Module,Clauses}] = ets:lookup(?COVER_CLAUSE_TABLE, Module), do_reset2(Clauses). do_reset2([{M,F,A,C,_L}|Clauses]) -> @@ -2147,10 +2250,19 @@ do_reset2([]) -> ok. do_clear(Module) -> - ets:match_delete(?COVER_TABLE, {Module,'_'}), + ets:match_delete(?COVER_CLAUSE_TABLE, {Module,'_'}), ets:match_delete(?COVER_TABLE, {#bump{module=Module},'_'}), ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}). +not_loaded(Module, unloaded, State) -> + do_clear(Module), + remote_unload(State#main_state.nodes,[Module]), + Compiled = update_compiled([Module], + State#main_state.compiled), + State#main_state{ compiled = Compiled }; +not_loaded(_Module,_Else, State) -> + State. + %%%--Div----------------------------------------------------------------- @@ -2172,7 +2284,36 @@ escape_lt_and_gt1([$<|T],Acc) -> escape_lt_and_gt1(T,[$;,$t,$l,$&|Acc]); escape_lt_and_gt1([$>|T],Acc) -> escape_lt_and_gt1(T,[$;,$t,$g,$&|Acc]); +escape_lt_and_gt1([$&|T],Acc) -> + escape_lt_and_gt1(T,[$;,$p,$m,$a,$&|Acc]); escape_lt_and_gt1([],Acc) -> lists:reverse(Acc); escape_lt_and_gt1([H|T],Acc) -> escape_lt_and_gt1(T,[H|Acc]). + +pmap(Fun, List) -> + pmap(Fun, List, 20). +pmap(Fun, List, Limit) -> + pmap(Fun, List, [], Limit, 0, []). +pmap(Fun, [E | Rest], Pids, Limit, Cnt, Acc) when Cnt < Limit -> + Collector = self(), + Pid = spawn_link(fun() -> + ?SPAWN_DBG(pmap,E), + Collector ! {res,self(),Fun(E)} + end), + erlang:monitor(process, Pid), + pmap(Fun, Rest, Pids ++ [Pid], Limit, Cnt + 1, Acc); +pmap(Fun, List, [Pid | Pids], Limit, Cnt, Acc) -> + receive + {'DOWN', _Ref, process, _, _} -> + pmap(Fun, List, [Pid | Pids], Limit, Cnt - 1, Acc); + {res, Pid, Res} -> + pmap(Fun, List, Pids, Limit, Cnt, [Res | Acc]) + end; +pmap(_Fun, [], [], _Limit, 0, Acc) -> + lists:reverse(Acc); +pmap(Fun, [], [], Limit, Cnt, Acc) -> + receive + {'DOWN', _Ref, process, _, _} -> + pmap(Fun, [], [], Limit, Cnt - 1, Acc) + end. diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl index b4313d6888..87fdc1fa34 100644 --- a/lib/tools/src/eprof.erl +++ b/lib/tools/src/eprof.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% Purpose: Profile a system in order to figure out where the @@ -23,456 +23,467 @@ -module(eprof). -behaviour(gen_server). --export([start/0, stop/0, dump/0, total_analyse/0, - start_profiling/1, profile/2, profile/4, profile/1, - stop_profiling/0, analyse/0, log/1]). +-export([start/0, + stop/0, + dump/0, + start_profiling/1, start_profiling/2, + profile/1, profile/2, profile/3, profile/4, profile/5, + stop_profiling/0, + analyze/0, analyze/1, analyze/2, + log/1]). %% Internal exports -export([init/1, - call/4, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-record(bpd, { + n = 0, % number of total calls + us = 0, % sum of uS for all calls + p = gb_trees:empty(), % tree of {Pid, {Mfa, {Count, Us}}} + mfa = [] % list of {Mfa, {Count, Us}} + }). + +-record(state, { + profiling = false, + pattern = {'_','_','_'}, + rootset = [], + fd = undefined, + start_ts = undefined, + reply = undefined, + bpd = #bpd{} + }). + + + +%% -------------------------------------------------------------------- %% +%% +%% API +%% +%% -------------------------------------------------------------------- %% --include_lib("stdlib/include/qlc.hrl"). - --import(lists, [flatten/1,reverse/1,keysort/2]). - - --record(state, {table = notable, - proc = noproc, - profiling = false, - pfunc = undefined, - pop = running, - ptime = 0, - overhead = 0, - rootset = []}). - -%%%%%%%%%%%%%% - -start() -> gen_server:start({local, eprof}, eprof, [], []). -stop() -> gen_server:call(eprof, stop, infinity). +start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). +stop() -> gen_server:call(?MODULE, stop, infinity). +profile(Fun) when is_function(Fun) -> + profile([], Fun); +profile(Rs) when is_list(Rs) -> + start_profiling(Rs). profile(Pids, Fun) -> - start(), - gen_server:call(eprof, {profile,Pids,erlang,apply,[Fun,[]]},infinity). + profile(Pids, Fun, {'_','_','_'}). + +profile(Pids, Fun, Pattern) -> + profile(Pids, erlang, apply, [Fun,[]], Pattern). profile(Pids, M, F, A) -> + profile(Pids, M, F, A, {'_','_','_'}). + +profile(Pids, M, F, A, Pattern) -> start(), - gen_server:call(eprof, {profile,Pids,M,F,A},infinity). + gen_server:call(?MODULE, {profile,Pids,Pattern,M,F,A},infinity). dump() -> - gen_server:call(eprof, dump, infinity). + gen_server:call(?MODULE, dump, infinity). -analyse() -> - gen_server:call(eprof, analyse, infinity). +analyze() -> + analyze(procs). -log(File) -> - gen_server:call(eprof, {logfile, File}, infinity). +analyze(Type) when is_atom(Type) -> + analyze(Type, []); +analyze(Opts) when is_list(Opts) -> + analyze(procs, Opts). +analyze(Type, Opts) when is_list(Opts) -> + gen_server:call(?MODULE, {analyze, Type, Opts}, infinity). -total_analyse() -> - gen_server:call(eprof, total_analyse, infinity). +log(File) -> + gen_server:call(?MODULE, {logfile, File}, infinity). start_profiling(Rootset) -> + start_profiling(Rootset, {'_','_','_'}). +start_profiling(Rootset, Pattern) -> start(), - gen_server:call(eprof, {profile, Rootset}, infinity). + gen_server:call(?MODULE, {profile, Rootset, Pattern}, infinity). stop_profiling() -> - gen_server:call(eprof, stop_profiling, infinity). + gen_server:call(?MODULE, stop_profiling, infinity). -profile(Rs) -> - start_profiling(Rs). -%%%%%%%%%%%%%%%% +%% -------------------------------------------------------------------- %% +%% +%% init +%% +%% -------------------------------------------------------------------- %% -init(_) -> +init([]) -> process_flag(trap_exit, true), - process_flag(priority, max), - put(three_one, {3,1}), %To avoid building garbage. {ok, #state{}}. -subtr({X1,Y1,Z1}, {X1,Y1,Z2}) -> - Z1 - Z2; -subtr({X1,Y1,Z1}, {X2,Y2,Z2}) -> - (((X1-X2) * 1000000) + Y1 - Y2) * 1000000 + Z1 - Z2. +%% -------------------------------------------------------------------- %% +%% +%% handle_call +%% +%% -------------------------------------------------------------------- %% -update_call_statistics(Tab, Key, Time) -> - try ets:update_counter(Tab, Key, Time) of - NewTime when is_integer(NewTime) -> - ets:update_counter(Tab, Key, get(three_one)) - catch - error:badarg -> - ets:insert(Tab, {Key,Time,1}) - end. +%% analyze -update_other_statistics(Tab, Key, Time) -> - try - ets:update_counter(Tab, Key, Time) - catch - error:badarg -> - ets:insert(Tab, {Key,Time,0}) - end. +handle_call({analyze, _, _}, _, #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0} = Bpd } = S) when is_record(Bpd, bpd) -> + {reply, nothing_to_analyze, S}; -do_messages({trace_ts,From,Op,Mfa,Time}, Tab, undefined,_PrevOp0,_PrevTime0) -> - PrevFunc = [From|Mfa], - receive - {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time) - after 0 -> - {PrevFunc,Op,Time} - end; -do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, call, PrevTime0) -> - update_call_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)), - PrevFunc = case Op of - exit -> undefined; - out -> undefined; - _ -> [From|Mfa] - end, - receive - {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time) - after 0 -> - {PrevFunc,Op,Time} - end; -do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, _PrevOp0, PrevTime0) -> - update_other_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)), - PrevFunc = case Op of - exit -> undefined; - out -> undefined; - _ -> [From|Mfa] - end, - receive - {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time) - after 0 -> - {PrevFunc,Op,Time} - end. +handle_call({analyze, procs, Opts}, _, #state{ bpd = #bpd{ p = Ps, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) -> + lists:foreach(fun + ({Pid, Mfas}) -> + {Pn, Pus} = sum_bp_total_n_us(Mfas), + format(Fd, "~n****** Process ~w -- ~s % of profiled time *** ~n", [Pid, s("~.2f", [100.0*divide(Pus,Tus)])]), + print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts), + ok + end, gb_trees:to_list(Ps)), + {reply, ok, S}; -%%%%%%%%%%%%%%%%%% +handle_call({analyze, total, Opts}, _, #state{ bpd = #bpd{ mfa = Mfas, n = Tn, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) -> + print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts), + {reply, ok, S}; -handle_cast(_Req, S) -> {noreply, S}. +handle_call({analyze, Type, _Opts}, _, S) -> + {reply, {error, {undefined, Type}}, S}; -terminate(_Reason,_S) -> - call_trace_for_all(false), - normal. +%% profile -%%%%%%%%%%%%%%%%%% +handle_call({profile, _Rootset, _Pattern, _M,_F,_A}, _From, #state{ profiling = true } = S) -> + {reply, {error, already_profiling}, S}; -handle_call({logfile, F}, _FromTag, Status) -> - case file:open(F, [write]) of - {ok, Fd} -> - case get(fd) of - undefined -> ok; - FdOld -> file:close(FdOld) - end, - put(fd, Fd), - {reply, ok, Status}; - {error, _} -> - {reply, error, Status} - end; +handle_call({profile, Rootset, Pattern, M,F,A}, From, #state{fd = Fd } = S) -> -handle_call({profile, Rootset}, {From, _Tag}, S) -> - link(From), - maybe_delete(S#state.table), - io:format("eprof: Starting profiling ..... ~n",[]), - ptrac(S#state.rootset, false, all()), - flush_receive(), - Tab = ets:new(eprof, [set, public]), - case ptrac(Rootset, true, all()) of - false -> - {reply, error, #state{}}; + set_pattern_trace(false, S#state.pattern), + set_process_trace(false, S#state.rootset), + + Pid = setup_profiling(M,F,A), + case set_process_trace(true, [Pid|Rootset]) of true -> - uni_schedule(), - call_trace_for_all(true), - erase(replyto), - {reply, profiling, #state{table = Tab, - proc = From, - profiling = true, - rootset = Rootset}} + set_pattern_trace(true, Pattern), + T0 = now(), + execute_profiling(Pid), + {noreply, #state{ + profiling = true, + rootset = [Pid|Rootset], + start_ts = T0, + reply = From, + fd = Fd, + pattern = Pattern + }}; + false -> + exit(Pid, eprof_kill), + {reply, error, #state{ fd = Fd}} end; -handle_call(stop_profiling, _FromTag, S) when S#state.profiling -> - ptrac(S#state.rootset, false, all()), - call_trace_for_all(false), - multi_schedule(), - io:format("eprof: Stop profiling~n",[]), - ets:delete(S#state.table, nofunc), - {reply, profiling_stopped, S#state{profiling = false}}; +handle_call({profile, _Rootset, _Pattern}, _From, #state{ profiling = true } = S) -> + {reply, {error, already_profiling}, S}; -handle_call(stop_profiling, _FromTag, S) -> - {reply, profiling_already_stopped, S}; +handle_call({profile, Rootset, Pattern}, From, #state{ fd = Fd } = S) -> + + set_pattern_trace(false, S#state.pattern), + set_process_trace(false, S#state.rootset), -handle_call({profile, Rootset, M, F, A}, FromTag, S) -> - io:format("eprof: Starting profiling..... ~n", []), - maybe_delete(S#state.table), - ptrac(S#state.rootset, false, all()), - flush_receive(), - put(replyto, FromTag), - Tab = ets:new(eprof, [set, public]), - P = spawn_link(eprof, call, [self(), M, F, A]), - case ptrac([P|Rootset], true, all()) of + case set_process_trace(true, Rootset) of true -> - uni_schedule(), - call_trace_for_all(true), - P ! {self(),go}, - {noreply, #state{table = Tab, - profiling = true, - rootset = [P|Rootset]}}; + T0 = now(), + set_pattern_trace(true, Pattern), + {reply, profiling, #state{ + profiling = true, + rootset = Rootset, + start_ts = T0, + reply = From, + fd = Fd, + pattern = Pattern + }}; false -> - exit(P, kill), - erase(replyto), - {reply, error, #state{}} + {reply, error, #state{ fd = Fd }} end; -handle_call(dump, _FromTag, S) -> - {reply, dump(S#state.table), S}; - -handle_call(analyse, _FromTag, S) -> - {reply, analyse(S), S}; +handle_call(stop_profiling, _From, #state{ profiling = false } = S) -> + {reply, profiling_already_stopped, S}; -handle_call(total_analyse, _FromTag, S) -> - {reply, total_analyse(S), S}; +handle_call(stop_profiling, _From, #state{ profiling = true } = S) -> -handle_call(stop, _FromTag, S) -> - multi_schedule(), - {stop, normal, stopped, S}. + set_pattern_trace(pause, S#state.pattern), -%%%%%%%%%%%%%%%%%%% + Bpd = collect_bpd(), -handle_info({trace_ts,_From,_Op,_Func,_Time}=M, S0) when S0#state.profiling -> - Start = erlang:now(), - #state{table=Tab,pop=PrevOp0,ptime=PrevTime0,pfunc=PrevFunc0, - overhead=Overhead0} = S0, - {PrevFunc,PrevOp,PrevTime} = do_messages(M, Tab, PrevFunc0, PrevOp0, PrevTime0), - Overhead = Overhead0 + subtr(erlang:now(), Start), - S = S0#state{overhead=Overhead,pfunc=PrevFunc,pop=PrevOp,ptime=PrevTime}, - {noreply,S}; + set_process_trace(false, S#state.rootset), + set_pattern_trace(false, S#state.pattern), -handle_info({trace_ts, From, _, _, _}, S) when not S#state.profiling -> - ptrac([From], false, all()), - {noreply, S}; + {reply, profiling_stopped, S#state{ + profiling = false, + rootset = [], + pattern = {'_','_','_'}, + bpd = Bpd + }}; -handle_info({_P, {answer, A}}, S) -> - ptrac(S#state.rootset, false, all()), - io:format("eprof: Stop profiling~n",[]), - {From,_Tag} = get(replyto), - catch unlink(From), - ets:delete(S#state.table, nofunc), - gen_server:reply(erase(replyto), {ok, A}), - multi_schedule(), - {noreply, S#state{profiling = false, - rootset = []}}; - -handle_info({'EXIT', P, Reason}, - #state{profiling=true,proc=P,table=T,rootset=RootSet}) -> - maybe_delete(T), - ptrac(RootSet, false, all()), - multi_schedule(), - io:format("eprof: Profiling failed\n",[]), - case erase(replyto) of - undefined -> - {noreply, #state{}}; - FromTag -> - gen_server:reply(FromTag, {error, Reason}), - {noreply, #state{}} +%% logfile +handle_call({logfile, File}, _From, #state{ fd = OldFd } = S) -> + case file:open(File, [write]) of + {ok, Fd} -> + case OldFd of + undefined -> ok; + OldFd -> file:close(OldFd) + end, + {reply, ok, S#state{ fd = Fd}}; + Error -> + {reply, Error, S} end; -handle_info({'EXIT',_P,_Reason}, S) -> - {noreply, S}. +handle_call(dump, _From, #state{ bpd = Bpd } = S) when is_record(Bpd, bpd) -> + {reply, gb_trees:to_list(Bpd#bpd.p), S}; -uni_schedule() -> - erlang:system_flag(multi_scheduling, block). +handle_call(stop, _FromTag, S) -> + {stop, normal, stopped, S}. -multi_schedule() -> - erlang:system_flag(multi_scheduling, unblock). +%% -------------------------------------------------------------------- %% +%% +%% handle_cast +%% +%% -------------------------------------------------------------------- %% -%%%%%%%%%%%%%%%%%% +handle_cast(_Msg, State) -> + {noreply, State}. -call(Top, M, F, A) -> - receive - {Top,go} -> - Top ! {self(), {answer, apply(M,F,A)}} - end. +%% -------------------------------------------------------------------- %% +%% +%% handle_info +%% +%% -------------------------------------------------------------------- %% -call_trace_for_all(Flag) -> - erlang:trace_pattern(on_load, Flag, [local]), - erlang:trace_pattern({'_','_','_'}, Flag, [local]). +handle_info({'EXIT', _, normal}, S) -> + {noreply, S}; +handle_info({'EXIT', _, eprof_kill}, S) -> + {noreply, S}; +handle_info({'EXIT', _, Reason}, #state{ reply = FromTag } = S) -> -ptrac([P|T], How, Flags) when is_pid(P) -> - case dotrace(P, How, Flags) of - true -> - ptrac(T, How, Flags); - false when How -> - false; - false -> - ptrac(T, How, Flags) - end; + set_process_trace(false, S#state.rootset), + set_pattern_trace(false, S#state.pattern), -ptrac([P|T], How, Flags) when is_atom(P) -> - case whereis(P) of - undefined when How -> - false; - undefined when not How -> - ptrac(T, How, Flags); - Pid -> - ptrac([Pid|T], How, Flags) - end; + gen_server:reply(FromTag, {error, Reason}), + {noreply, S#state{ + profiling = false, + rootset = [], + pattern = {'_','_','_'} + }}; -ptrac([H|_],_How,_Flags) -> - io:format("** eprof bad process ~w~n",[H]), - false; +% check if Pid is spawned process? +handle_info({_Pid, {answer, Result}}, #state{ reply = {From,_} = FromTag} = S) -> -ptrac([],_,_) -> true. + set_pattern_trace(pause, S#state.pattern), -dotrace(P, How, What) -> - case (catch erlang:trace(P, How, What)) of - 1 -> - true; - _Other when not How -> - true; - _Other -> - io:format("** eprof: bad process: ~p,~p,~p~n", [P,How,What]), - false - end. + Bpd = collect_bpd(), -all() -> [call,arity,return_to,running,timestamp,set_on_spawn]. - -total_analyse(#state{table=notable}) -> - nothing_to_analyse; -total_analyse(S) -> - #state{table = T, overhead = Overhead} = S, - QH = qlc:q([{{From,Mfa},Time,Count} || - {[From|Mfa],Time,Count} <- ets:table(T)]), - Pcalls = reverse(keysort(2, replicas(qlc:eval(QH)))), - Time = collect_times(Pcalls), - format("FUNCTION~44s TIME ~n", ["CALLS"]), - printit(Pcalls, Time), - format("\nTotal time: ~.2f\n", [Time / 1000000]), - format("Measurement overhead: ~.2f\n", [Overhead / 1000000]). - -analyse(#state{table=notable}) -> - nothing_to_analyse; -analyse(S) -> - #state{table = T, overhead = Overhead} = S, - Pids = ordsets:from_list(flatten(ets:match(T, {['$1'|'_'],'_', '_'}))), - Times = sum(ets:match(T, {'_','$1', '_'})), - format("FUNCTION~44s TIME ~n", ["CALLS"]), - do_pids(Pids, T, 0, Times), - format("\nTotal time: ~.2f\n", [Times / 1000000]), - format("Measurement overhead: ~.2f\n", [Overhead / 1000000]). - -do_pids([Pid|Tail], T, AckTime, Total) -> - Pcalls = - reverse(keysort(2, to_tups(ets:match(T, {[Pid|'$1'], '$2','$3'})))), - Time = collect_times(Pcalls), - PercentTotal = 100 * (divide(Time, Total)), - format("~n****** Process ~w -- ~s % of profiled time *** ~n", - [Pid, fpf(PercentTotal)]), - printit(Pcalls, Time), - do_pids(Tail, T, AckTime + Time, Total); -do_pids([], _, _, _) -> - ok. + set_process_trace(false, S#state.rootset), + set_pattern_trace(false, S#state.pattern), -printit([],_) -> ok; -printit([{{Mod,Fun,Arity}, Time, Calls} |Tail], ProcTime) -> - format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls), - fpf(100*(divide(Time,ProcTime)))]), - printit(Tail, ProcTime); -printit([{{_,{Mod,Fun,Arity}}, Time, Calls} |Tail], ProcTime) -> - format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls), - fpf(100*(divide(Time,ProcTime)))]), - printit(Tail, ProcTime); -printit([_|T], Time) -> - printit(T, Time). - -ff(Mod,Fun,Arity) -> - pad(flatten(io_lib:format("~w:~w/~w", [Mod,Fun, Arity])),45). - -pad(Str, Len) -> - Strlen = length(Str), - if - Strlen > Len -> strip_tail(Str, 45); - true -> lists:append(Str, mklist(Len-Strlen)) - end. + catch unlink(From), + gen_server:reply(FromTag, {ok, Result}), + {noreply, S#state{ + profiling = false, + rootset = [], + pattern = {'_','_','_'}, + bpd = Bpd + }}. + +%% -------------------------------------------------------------------- %% +%% +%% termination +%% +%% -------------------------------------------------------------------- %% + +terminate(_Reason, #state{ fd = undefined }) -> + set_pattern_trace(false, {'_','_','_'}), + ok; +terminate(_Reason, #state{ fd = Fd }) -> + file:close(Fd), + set_pattern_trace(false, {'_','_','_'}), + ok. -strip_tail([_|_], 0) ->[]; -strip_tail([H|T], I) -> [H|strip_tail(T, I-1)]; -strip_tail([],_I) -> []. +%% -------------------------------------------------------------------- %% +%% +%% code_change +%% +%% -------------------------------------------------------------------- %% -fpf(F) -> strip_tail(flatten(io_lib:format("~w", [round(F)])), 5). -fint(Int) -> pad(flatten(io_lib:format("~w",[Int])), 10). +code_change(_OldVsn, State, _Extra) -> + {ok, State}. -mklist(0) -> []; -mklist(I) -> [$ |mklist(I-1)]. -to_tups(L) -> lists:map(fun(List) -> erlang:list_to_tuple(List) end, L). +%% -------------------------------------------------------------------- %% +%% +%% AUX Functions +%% +%% -------------------------------------------------------------------- %% -divide(X,Y) -> X / Y. +setup_profiling(M,F,A) -> + spawn_link(fun() -> spin_profile(M,F,A) end). -collect_times([]) -> 0; -collect_times([Tup|Tail]) -> element(2, Tup) + collect_times(Tail). +spin_profile(M, F, A) -> + receive + {Pid, execute} -> + Pid ! {self(), {answer, erlang:apply(M,F,A)}} + end. -dump(T) -> - L = ets:tab2list(T), - format(L). +execute_profiling(Pid) -> + Pid ! {self(), execute}. -format([H|T]) -> - format("~p~n", [H]), format(T); -format([]) -> ok. +set_pattern_trace(Flag, Pattern) -> + erlang:system_flag(multi_scheduling, block), + erlang:trace_pattern(on_load, Flag, [call_time]), + erlang:trace_pattern(Pattern, Flag, [call_time]), + erlang:system_flag(multi_scheduling, unblock), + ok. -format(F, A) -> - io:format(F,A), - case get(fd) of - undefined -> ok; - Fd -> io:format(Fd, F,A) +set_process_trace(Flag, Pids) -> + % do we need procs for meta info? + % could be useful + set_process_trace(Flag, Pids, [call, set_on_spawn]). +set_process_trace(_, [], _) -> true; +set_process_trace(Flag, [Pid|Pids], Options) when is_pid(Pid) -> + try + erlang:trace(Pid, Flag, Options), + set_process_trace(Flag, Pids, Options) + catch + _:_ -> + false + end; +set_process_trace(Flag, [Name|Pids], Options) when is_atom(Name) -> + case whereis(Name) of + undefined -> + set_process_trace(Flag, Pids, Options); + Pid -> + set_process_trace(Flag, [Pid|Pids], Options) end. -maybe_delete(T) -> - catch ets:delete(T). +collect_bpd() -> + collect_bpd([M || M <- [element(1, Mi) || Mi <- code:all_loaded()], M =/= ?MODULE]). + +collect_bpd(Ms) when is_list(Ms) -> + collect_bpdf(collect_mfas(Ms) ++ erlang:system_info(snifs)). + +collect_mfas(Ms) -> + lists:foldl(fun + (M, Mfas) -> + Mfas ++ [{M, F, A} || {F, A} <- M:module_info(functions)] + end, [], Ms). + +collect_bpdf(Mfas) -> + collect_bpdf(Mfas, #bpd{}). +collect_bpdf([], Bpd) -> + Bpd; +collect_bpdf([Mfa|Mfas], #bpd{n = N, us = Us, p = Tree, mfa = Code } = Bpd) -> + case erlang:trace_info(Mfa, call_time) of + {call_time, []} -> + collect_bpdf(Mfas, Bpd); + {call_time, Data} when is_list(Data) -> + {CTn, CTus, CTree} = collect_bpdfp(Mfa, Tree, Data), + collect_bpdf(Mfas, Bpd#bpd{ + n = CTn + N, + us = CTus + Us, + p = CTree, + mfa = [{Mfa, {CTn, CTus}}|Code] + }); + {call_time, false} -> + collect_bpdf(Mfas, Bpd); + {call_time, _Other} -> + collect_bpdf(Mfas, Bpd) + end. -sum([[H]|T]) -> H + sum(T); -sum([]) -> 0. +collect_bpdfp(Mfa, Tree, Data) -> + lists:foldl(fun + ({Pid, Ni, Si, Usi}, {PTno, PTuso, To}) -> + Time = Si * 1000000 + Usi, + Ti1 = case gb_trees:lookup(Pid, To) of + none -> + gb_trees:enter(Pid, [{Mfa, {Ni, Time}}], To); + {value, Pmfas} -> + gb_trees:enter(Pid, [{Mfa, {Ni, Time}}|Pmfas], To) + end, + {PTno + Ni, PTuso + Time, Ti1} + end, {0,0, Tree}, Data). + +%% manipulators +sort_mfa(Bpfs, mfa) when is_list(Bpfs) -> + lists:sort(fun + ({A,_}, {B,_}) when A < B -> true; + (_, _) -> false + end, Bpfs); +sort_mfa(Bpfs, time) when is_list(Bpfs) -> + lists:sort(fun + ({_,{_,A}}, {_,{_,B}}) when A < B -> true; + (_, _) -> false + end, Bpfs); +sort_mfa(Bpfs, calls) when is_list(Bpfs) -> + lists:sort(fun + ({_,{A,_}}, {_,{B,_}}) when A < B -> true; + (_, _) -> false + end, Bpfs); +sort_mfa(Bpfs, _) when is_list(Bpfs) -> sort_mfa(Bpfs, time). + +filter_mfa(Bpfs, Ts) when is_list(Ts) -> + filter_mfa(Bpfs, [], proplists:get_value(calls, Ts, 0), proplists:get_value(time, Ts, 0)); +filter_mfa(Bpfs, _) -> Bpfs. +filter_mfa([], Out, _, _) -> lists:reverse(Out); +filter_mfa([{_, {C, T}}=Bpf|Bpfs], Out, Ct, Tt) when C >= Ct, T >= Tt -> filter_mfa(Bpfs, [Bpf|Out], Ct, Tt); +filter_mfa([_|Bpfs], Out, Ct, Tt) -> filter_mfa(Bpfs, Out, Ct, Tt). + +sum_bp_total_n_us(Mfas) -> + lists:foldl(fun ({_, {Ci,Usi}}, {Co, Uso}) -> {Co + Ci, Uso + Usi} end, {0,0}, Mfas). + +%% strings and format + +string_bp_mfa(Mfas, Tus) -> string_bp_mfa(Mfas, Tus, {0,0,0,0,0}, []). +string_bp_mfa([], _, Ws, Strings) -> {Ws, lists:reverse(Strings)}; +string_bp_mfa([{Mfa, {Count, Time}}|Mfas], Tus, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) -> + Smfa = s(Mfa), + Scount = s(Count), + Stime = s(Time), + Sperc = s("~.2f", [100*divide(Time,Tus)]), + Stpc = s("~.2f", [divide(Time,Count)]), + + string_bp_mfa(Mfas, Tus, { + erlang:max(MfaW, length(Smfa)), + erlang:max(CountW,length(Scount)), + erlang:max(PercW, length(Sperc)), + erlang:max(TimeW, length(Stime)), + erlang:max(TpCW, length(Stpc)) + }, [[Smfa, Scount, Sperc, Stime, Stpc] | Strings]). + +print_bp_mfa(Mfas, {_Tn, Tus}, Fd, Opts) -> + Fmfas = filter_mfa(sort_mfa(Mfas, proplists:get_value(sort, Opts)), proplists:get_value(filter, Opts)), + {{MfaW, CountW, PercW, TimeW, TpCW}, Strs} = string_bp_mfa(Fmfas, Tus), + Ws = { + erlang:max(length("FUNCTION"), MfaW), + erlang:max(length("CALLS"), CountW), + erlang:max(length(" %"), PercW), + erlang:max(length("TIME"), TimeW), + erlang:max(length("uS / CALLS"), TpCW) + }, + format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]), + format(Fd, Ws, ["--------", "-----", "---", "----", "----------"]), + + lists:foreach(fun (String) -> format(Fd, Ws, String) end, Strs), + ok. -replicas(L) -> - replicas(L, []). +s({M,F,A}) -> s("~w:~w/~w",[M,F,A]); +s(Term) -> s("~p", [Term]). +s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). -replicas([{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Tail], Result) -> - case search({Mod,Fun,Arity},Result) of - false -> - replicas(Tail, [{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Result]); - {Ack2, Calls2} -> - Result2 = del({Mod,Fun,Arity}, Result), - replicas(Tail, [{{Pid, {Mod,Fun,Arity}}, - Ack+Ack2,Calls+Calls2} |Result2]) - end; -replicas([_|T], Ack) -> %% Whimpy - replicas(T, Ack); - -replicas([], Res) -> Res. - -search(Key, [{{_,Key}, Ack, Calls}|_]) -> - {Ack, Calls}; -search(Key, [_|T]) -> - search(Key, T); -search(_Key,[]) -> false. - -del(Key, [{{_,Key},_Ack,_Calls}|T]) -> - T; -del(Key, [H | Tail]) -> - [H|del(Key, Tail)]; -del(_Key,[]) -> []. - -flush_receive() -> - receive - {trace_ts, From, _, _, _} when is_pid(From) -> - ptrac([From], false, all()), - flush_receive(); - _ -> - flush_receive() - after 0 -> - ok - end. +format(Fd, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) -> + format(Fd, s("~~.~ps ~~~ps ~~~ps ~~~ps [~~~ps]~~n", [MfaW, CountW, PercW, TimeW, TpCW]), Strings); +format(undefined, Format, Strings) -> + io:format(Format, Strings), + ok; +format(Fd, Format, Strings) -> + io:format(Fd, Format, Strings), + io:format(Format, Strings), + ok. -code_change(_OldVsn, State, _Extra) -> - {ok,State}. +divide(_,0) -> 0.0; +divide(T,N) -> T/N. diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl index d0dbf4a2b4..93f0e9c0c8 100644 --- a/lib/tools/src/xref_base.erl +++ b/lib/tools/src/xref_base.erl @@ -1,24 +1,26 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(xref_base). +%% Avoid warning for local function error/1 clashing with autoimported BIF. +-compile({no_auto_import,[error/1]}). -export([new/0, new/1, delete/1, add_directory/2, add_directory/3, add_module/2, add_module/3, @@ -29,7 +31,7 @@ add_release/2, add_release/3, get_library_path/1, set_library_path/2, set_library_path/3, set_up/1, set_up/2, - q/2, q/3, info/1, info/2, info/3, update/1, update/2, + q/2, q/3, info/1, info/2, info/3, update/1, update/2, forget/1, forget/2, variables/1, variables/2, analyze/2, analyze/3, analysis/1, get_default/2, set_default/3, @@ -38,14 +40,14 @@ -export([format_error/1]). %% The following functions are exported for testing purposes only: --export([do_add_module/4, do_add_application/2, do_add_release/2, +-export([do_add_module/4, do_add_application/2, do_add_release/2, do_remove_module/2]). --import(lists, - [filter/2, flatten/1, foldl/3, keysearch/3, map/2, mapfoldl/3, - member/2, reverse/1, sort/1, usort/1]). +-import(lists, + [filter/2, flatten/1, foldl/3, foreach/2, keysearch/3, map/2, + mapfoldl/3, member/2, reverse/1, sort/1, usort/1]). --import(sofs, +-import(sofs, [constant_function/2, converse/1, difference/2, domain/1, empty_set/0, family/1, family_difference/2, intersection/2, family_projection/2, family_to_relation/1, family_union/1, @@ -103,12 +105,12 @@ delete(State) -> ok end end, - map(Fun, dict:to_list(State#xref.variables)), + foreach(Fun, dict:to_list(State#xref.variables)), ok. add_directory(State, Dir) -> add_directory(State, Dir, []). - + %% -> {ok, Modules, NewState} | Error add_directory(State, Dir, Options) -> ValOptions = option_values([builtins, recurse, verbose, warnings], State), @@ -277,7 +279,7 @@ q(S, Q, Options) when is_atom(Q) -> q(S, atom_to_list(Q), Options); q(S, Q, Options) -> case xref_utils:is_string(Q, 1) of - true -> + true -> case set_up(S, Options) of {ok, S1} -> case xref_compiler:compile(Q, S1#xref.variables) of @@ -336,7 +338,7 @@ forget(State, Variable) when is_atom(Variable) -> forget(State, Variables) -> Vars = State#xref.variables, do_forget(Variables, Vars, Variables, State). - + variables(State) -> variables(State, [user]). @@ -350,9 +352,9 @@ variables(State, Options) -> {ok, NewState} -> {U, P} = do_variables(NewState), R1 = if User -> [{user, U}]; true -> [] end, - R = if - Predef -> [{predefined,P} | R1]; - true -> R1 + R = if + Predef -> [{predefined,P} | R1]; + true -> R1 end, {{ok, R}, NewState}; Error -> @@ -368,7 +370,7 @@ analyze(State, Analysis) -> %% -> {{ok, Answer}, NewState} | {Error, NewState} analyze(State, Analysis, Options) -> case analysis(Analysis, State#xref.mode) of - P when is_list(P) -> + P when is_list(P) -> q(State, P, Options); error -> R = case analysis(Analysis, functions) of @@ -461,7 +463,7 @@ get_default(State, Option) -> %% -> [{Option, Value}] get_default(State) -> - Fun = fun(O) -> V = current_default(State, O), {O, V} end, + Fun = fun(O) -> V = current_default(State, O), {O, V} end, map(Fun, [builtins, recurse, verbose, warnings]). %% -> {ok, NewState} -> Error @@ -478,7 +480,7 @@ set_default(State, Options) -> format_error({error, Module, Error}) -> Module:format_error(Error); format_error({invalid_options, Options}) -> - io_lib:format("Unknown option(s) or invalid option value(s): ~p~n", + io_lib:format("Unknown option(s) or invalid option value(s): ~p~n", [Options]); format_error({invalid_filename, Term}) -> io_lib:format("A file name (a string) was expected: ~p~n", [Term]); @@ -540,7 +542,7 @@ updated_modules(State) -> case xref_utils:file_info(File) of {ok, {_, file, readable, MTime}} when MTime =/= RTime -> [{M,File} | L]; - _Else -> + _Else -> L end end, @@ -591,7 +593,7 @@ do_add_release(Dir, RelName, OB, OV, OW, State) -> case xref_utils:release_directory(Dir, true, "ebin") of {ok, ReleaseDirName, ApplDir, Dirs} -> ApplDirs = xref_utils:select_last_application_version(Dirs), - Release = case RelName of + Release = case RelName of [[]] -> ReleaseDirName; [Name] -> Name end, @@ -615,7 +617,7 @@ do_add_release(S, XRel) -> end. add_rel_appls([ApplDir | ApplDirs], Release, OB, OV, OW, State) -> - {ok, _AppName, NewState} = + {ok, _AppName, NewState} = add_appldir(ApplDir, Release, [[]], OB, OV, OW, State), add_rel_appls(ApplDirs, Release, OB, OV, OW, NewState); add_rel_appls([], [Release], _OB, _OV, _OW, NewState) -> @@ -637,10 +639,10 @@ add_appldir(ApplDir, Release, Name, OB, OV, OW, OldState) -> [[]] -> AppName0; [N] -> N end, - AppInfo = #xref_app{name = AppName, rel_name = Release, + AppInfo = #xref_app{name = AppName, rel_name = Release, vsn = Vsn, dir = Dir}, State1 = do_add_application(OldState, AppInfo), - {ok, _Modules, NewState} = + {ok, _Modules, NewState} = do_add_directory(Dir, [AppName], OB, false, OV, OW, State1), {ok, AppName, NewState}. @@ -662,7 +664,7 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) -> ok = is_filename(Dir), {FileNames, Errors, Jams, Unreadable} = xref_utils:scan_directory(Dir, Rec, [?Suffix], [".jam"]), - warnings(War, jam, Jams), + warnings(War, jam, Jams), warnings(War, unreadable, Unreadable), case Errors of [] -> @@ -683,7 +685,7 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) -> false -> throw_error({invalid_filename, File}); Splitname -> - do_add_module(Splitname, AppName, Builtins, Verbose, + do_add_module(Splitname, AppName, Builtins, Verbose, Warnings, State) end. @@ -691,7 +693,7 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) -> %% Options: verbose, warnings, builtins do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) -> File = filename:join(Dir, Basename), - {ok, M, Bad, NewState} = + {ok, M, Bad, NewState} = do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State), filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad), {ok, M, NewState}. @@ -723,7 +725,7 @@ do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) -> {ok, {_, _, _, Time}} -> Time; Error -> throw(Error) end, - XMod = #xref_mod{name = M, app_name = AppName, dir = Dir, + XMod = #xref_mod{name = M, app_name = AppName, dir = Dir, mtime = T, builtins = Builtins, no_unresolved = NoUnresCalls}, do_add_module(State, XMod, UnresCalls, Data); @@ -736,13 +738,13 @@ abst(File, Builtins, Mode) when Mode =:= functions -> case beam_lib:chunks(File, [abstract_code, exports, attributes]) of {ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code -> {ok, M, NoA}; - {ok, {M, [{abstract_code, {abstract_v1, Forms}}, + {ok, {M, [{abstract_code, {abstract_v1, Forms}}, {exports,X0}, {attributes,A}]}} -> %% R7. X = xref_utils:fa_to_mfa(X0, M), D = deprecated(A, X, M), xref_reader:module(M, Forms, Builtins, X, D); - {ok, {M, [{abstract_code, {abstract_v2, Forms}}, + {ok, {M, [{abstract_code, {abstract_v2, Forms}}, {exports,X0}, {attributes,A}]}} -> %% R8-R9B. X = xref_utils:fa_to_mfa(X0, M), @@ -769,8 +771,8 @@ abst(File, Builtins, Mode) when Mode =:= modules -> true -> I0; false -> - Fun = fun({M,F,A}) -> - not xref_utils:is_builtin(M, F, A) + Fun = fun({M,F,A}) -> + not xref_utils:is_builtin(M, F, A) end, filter(Fun, I0) end, @@ -790,7 +792,7 @@ mfa_exports(X0, Attributes, M) -> xref_utils:fa_to_mfa(X1, M). adjust_arity(F, A) -> - case xref_utils:is_static_function(F, A) of + case xref_utils:is_static_function(F, A) of true -> A; false -> A - 1 end. @@ -885,7 +887,7 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions -> Unres = domain(UnresCalls), DefinedFuns = domain(DefAt), - {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} = + {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} = extra_edges(AXC1, ALC1, Bad0, DefinedFuns), Bad = map(fun(B) -> {xref_attr, B} end, Bad1), LPreCAt = union(LPreCAt1, LPreCAt2), @@ -904,8 +906,8 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions -> %% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt), Self = self(), - Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end, - {EE, ECallAt} = + Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end, + {EE, ECallAt} = xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]), [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2, @@ -977,13 +979,13 @@ extra_edges(CAX, CAL, Bad0, F) -> ALC = restriction(2, restriction(ALC0, F), F), LPreCAt2 = restriction(CAL, ALC), XPreCAt2 = restriction(CAX, AXC), - Bad = Bad0 ++ to_external(difference(AXC0, AXC)) + Bad = Bad0 ++ to_external(difference(AXC0, AXC)) ++ to_external(difference(ALC0, ALC)), {AXC, ALC, Bad, LPreCAt2, XPreCAt2}. no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) -> NoUnres = no_elements(Unres), - [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}}, + [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}}, {no_function_calls, {no_elements(LC), no_elements(XC)-NoUnres, NoUnres}}, {no_functions, {no_elements(L), no_elements(X)}}, %% Note: this is overwritten in do_set_up(): @@ -1011,10 +1013,10 @@ inter_graph(X, L, LC, XC, CallAt) -> Es = union(LEs, XEs), E1 = to_external(restriction(difference(LC, LEs), XL)), - R0 = xref_utils:xset(reachable(E1, G, []), + R0 = xref_utils:xset(reachable(E1, G, []), [{tspec(func), tspec(fun_edge)}]), true = digraph:delete(G), - + % RL is a set of indirect local calls to exports. RL = restriction(R0, XL), % RX is a set of indirect external calls to exports. @@ -1033,7 +1035,7 @@ inter_graph(X, L, LC, XC, CallAt) -> ?FORMAT("XL=~p~nXEs=~p~nLEs=~p~nE1=~p~nR0=~p~nRL=~p~nRX=~p~nR=~p~n" "EE=~p~nECallAt1=~p~nECallAt2=~p~nECallAt=~p~n~n", - [XL, XEs, LEs, E1, R0, RL, RX, R, EE, + [XL, XEs, LEs, E1, R0, RL, RX, R, EE, ECallAt1, ECallAt2, ECallAt]), {EE, ECallAt}. @@ -1121,7 +1123,7 @@ remove_erase([], D) -> do_add_libraries(Path, Verbose, State) -> message(Verbose, lib_search, []), - {C, E} = xref_utils:list_path(Path, [?Suffix]), + {C, E} = xref_utils:list_path(Path, [?Suffix]), message(Verbose, done, []), MDs = to_external(relation_to_family(relation(C))), %% message(Verbose, lib_check, []), @@ -1160,23 +1162,23 @@ do_set_up(S, VerboseOpt) -> Reply. %% If data has been supplied using add_module/9 (and that is the only -%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC, -%% and LU are guaranteed to be functions (with all supplied -%% modules as domain (disregarding unknown modules, that is, modules +%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC, +%% and LU are guaranteed to be functions (with all supplied +%% modules as domain (disregarding unknown modules, that is, modules %% not supplied but hosting unknown functions)). %% As a consequence, V and E are also functions. V is defined for unknown %% modules also. %% UU is also a function (thanks to sofs:family_difference/2...). -%% XU on the other hand can be a partial function (that is, not defined +%% XU on the other hand can be a partial function (that is, not defined %% for all modules). U is derived from XU, so U is also partial. %% The inverse variables - LC_1, XC_1, E_1 and EE_1 - are all partial. %% B is also partial. do_set_up(S) when S#xref.mode =:= functions -> ModDictList = dict:to_list(S#xref.modules), - [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU, + [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU, EE0, ECallAt, UC, LPredefined, Mod_DF,Mod_DF_1,Mod_DF_2,Mod_DF_3] = make_families(ModDictList, 18), - + {XC_1, XU, XPredefined} = do_set_up_1(XC), LC_1 = user_family(union_of_family(LC)), E_1 = family_union(XC_1, LC_1), @@ -1206,7 +1208,7 @@ do_set_up(S) when S#xref.mode =:= functions -> AM = domain(F1), %% Undef is the union of U0 and Lib: - {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} = + {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} = make_libs(XU, F1, AM, S#xref.library_path, S#xref.libraries), {B, U} = make_builtins(U0), X1_B = family_union(X1, B), @@ -1228,22 +1230,22 @@ do_set_up(S) when S#xref.mode =:= functions -> %% way to discard calls to local functions in other modules. EE_conv = converse(union_of_family(EE0)), EE_exported = restriction(EE_conv, union_of_family(X)), - EE_local = + EE_local = specification({external, fun({{M1,_,_},{M2,_,_}}) -> M1 =:= M2 end}, EE_conv), EE_0 = converse(union(EE_local, EE_exported)), EE_1 = user_family(EE_0), - EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end}, + EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end}, EE_0), %% Make sure EE is defined for all modules: EE = family_union(family_difference(EE0, EE0), EE1), - IFun = - fun({Mod,EE_M}, XMods) -> - IMFun = + IFun = + fun({Mod,EE_M}, XMods) -> + IMFun = fun(XrefMod) -> - [NoCalls, NoFunctionCalls, + [NoCalls, NoFunctionCalls, NoFunctions, _NoInter] = XrefMod#xref_mod.info, - NewInfo = [NoCalls, NoFunctionCalls, NoFunctions, + NewInfo = [NoCalls, NoFunctionCalls, NoFunctions, {no_inter_function_calls,length(EE_M)}], XrefMod#xref_mod{info = NewInfo} end, @@ -1274,11 +1276,11 @@ do_set_up(S) when S#xref.mode =:= functions -> finish_set_up(S1, Vs); do_set_up(S) when S#xref.mode =:= modules -> ModDictList = dict:to_list(S#xref.modules), - [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] = + [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] = make_families(ModDictList, 7), I = union_of_family(I0), AM = domain(X0), - + {XU, Predefined} = make_predefined(I, AM), %% Add "hidden" functions to the exports. X1 = family_union(X0, Predefined), @@ -1288,8 +1290,8 @@ do_set_up(S) when S#xref.mode =:= modules -> M2A = make_M2A(ModDictList), {A2R,A} = make_A2R(S#xref.applications), R = set(dict:fetch_keys(S#xref.releases)), - - ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end}, + + ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end}, family_to_relation(I0)), ME2AE = multiple_relative_product({M2A, M2A}, ME), @@ -1298,7 +1300,7 @@ do_set_up(S) when S#xref.mode =:= modules -> RE = range(AE2RE), %% Undef is the union of U0 and Lib: - {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} = + {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} = make_libs(XU, X1, AM, S#xref.library_path, S#xref.libraries), {B, U} = make_builtins(U0), X1_B = family_union(X1, B), @@ -1312,7 +1314,7 @@ do_set_up(S) when S#xref.mode =:= modules -> X = family_union(X1, Lib), Empty = empty_set(), - Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V}, + Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V}, {e,{Empty,Empty}}, {'M',M},{'A',A},{'R',R}, {'AM',AM},{'UM',UM},{'LM',LM}, @@ -1328,10 +1330,10 @@ finish_set_up(S, Vs) -> S1 = S#xref{variables = T}, %% io:format("~p <= state <= ~p~n", [pack:lsize(S), pack:usize(S)]), {ok, S1}. - + do_finish_set_up([{Key, Value} | Vs], T) -> {Type, OType} = var_type(Key), - Val = #xref_var{name = Key, value = Value, vtype = predef, + Val = #xref_var{name = Key, value = Value, vtype = predef, otype = OType, type = Type}, T1 = dict:store(Key, Val, T), do_finish_set_up(Vs, T1); @@ -1362,15 +1364,15 @@ var_type('EE') -> {function, edge}; var_type('LC') -> {function, edge}; var_type('UC') -> {function, edge}; var_type('XC') -> {function, edge}; -var_type('AE') -> {application, edge}; -var_type('ME') -> {module, edge}; +var_type('AE') -> {application, edge}; +var_type('ME') -> {module, edge}; var_type('RE') -> {release, edge}; var_type(_) -> {foo, bar}. make_families(ModDictList, N) -> Fun1 = fun({_,XMod}) -> XMod#xref_mod.data end, Ss = from_sets(map(Fun1, ModDictList)), - %% io:format("~n~p <= module data <= ~p~n", + %% io:format("~n~p <= module data <= ~p~n", %% [pack:lsize(Ss), pack:usize(Ss)]), make_fams(N, Ss, []). @@ -1389,7 +1391,7 @@ make_M2A(ModDictList) -> make_A2R(ApplDict) -> AppDict = dict:to_list(ApplDict), Fun = fun({A,XApp}) -> {A, XApp#xref_app.rel_name} end, - Appl0 = family(map(Fun, AppDict)), + Appl0 = family(map(Fun, AppDict)), AllApps = domain(Appl0), Appl = family_to_relation(Appl0), {Appl, AllApps}. @@ -1445,13 +1447,13 @@ make_libs(XU, F, AM, LibPath, LibDict) -> false -> Libraries = dict:to_list(LibDict), Lb = restriction(a_function(Libraries), UM), - MFun = fun({M,XLib}) -> + MFun = fun({M,XLib}) -> #xref_lib{dir = Dir} = XLib, xref_utils:module_filename(Dir, M) end, map(MFun, to_external(Lb)) end, - Fun = fun(FileName, Deprs) -> + Fun = fun(FileName, Deprs) -> case beam_lib:chunks(FileName, [exports, attributes]) of {ok, {M, [{exports,X}, {attributes,A}]}} -> Exports = mfa_exports(X, A, M), @@ -1496,14 +1498,14 @@ user_family(R) -> partition_family({external, fun({_MFA1, {M2,_,_}}) -> M2 end}, R). do_variables(State) -> - Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) -> + Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) -> {P,[Name | U]}; - ({Name, #xref_var{vtype = predef}}, A={P,U}) -> + ({Name, #xref_var{vtype = predef}}, A={P,U}) -> case atom_to_list(Name) of [H|_] when H>= $a, H=<$z -> A; _Else -> {[Name | P], U} end; - ({{tmp, V}, _}, A) -> + ({{tmp, V}, _}, A) -> io:format("Bug in ~p: temporary ~p~n", [?MODULE, V]), A; (_V, A) -> A end, @@ -1565,7 +1567,7 @@ do_info(S, libraries) -> map(fun({_L,XLib}) -> lib_info(XLib) end, D); do_info(_S, I) -> error({no_such_info, I}). - + do_info(S, Type, E) when is_atom(E) -> do_info(S, Type, [E]); do_info(S, modules, Modules0) when is_list(Modules0) -> @@ -1598,7 +1600,7 @@ find_info([E | Es], Dict, Error) -> {ok, X} -> [X | find_info(Es, Dict, Error)] end; -find_info([], _Dict, _Error) -> +find_info([], _Dict, _Error) -> []. %% -> {[{AppName, RelName}], [{RelName, XApp}]} @@ -1618,7 +1620,7 @@ rel_apps(S) -> rel_apps_sums(AR, RRA0, S) -> AppMods = app_mods(S), % [{AppName, XMod}] RRA1 = relation_to_family(relation(RRA0)), - RRA = inverse(substitution(1, RRA1)), + RRA = inverse(substitution(1, RRA1)), %% RRA is [{RelName,{RelName,[XApp]}}] RelMods = relative_product1(relation(AR), relation(AppMods)), RelAppsMods = relative_product1(RRA, RelMods), @@ -1630,7 +1632,7 @@ rel_apps_sums(AR, RRA0, S) -> %% -> [{AppName, XMod}] app_mods(S) -> D = sort(dict:to_list(S#xref.modules)), - Fun = fun({_M,XMod}, Acc) -> + Fun = fun({_M,XMod}, Acc) -> case XMod#xref_mod.app_name of [] -> Acc; [AppName] -> [{AppName, XMod} | Acc] @@ -1639,7 +1641,7 @@ app_mods(S) -> foldl(Fun, [], D). mod_info(XMod) -> - #xref_mod{name = M, app_name = AppName, builtins = BuiltIns, + #xref_mod{name = M, app_name = AppName, builtins = BuiltIns, dir = Dir, info = Info} = XMod, App = sup_info(AppName), {M, [{application, App}, {builtins, BuiltIns}, {directory, Dir} | Info]}. @@ -1649,7 +1651,7 @@ app_info({AppName, ModSums}, S) -> #xref_app{rel_name = RelName, vsn = Vsn, dir = Dir} = XApp, Release = sup_info(RelName), {AppName, [{directory,Dir}, {release, Release}, {version,Vsn} | ModSums]}. - + rel_info({{RelName, XApps}, ModSums}, S) -> NoApps = length(XApps), XRel = dict:fetch(RelName, S#xref.releases), @@ -1678,16 +1680,16 @@ no_sum(S, L) when S#xref.mode =:= modules -> [{no_analyzed_modules, length(L)}]. no_sum([XMod | D], C0, UC0, LC0, XC0, UFC0, L0, X0, EV0, NoM) -> - [{no_calls, {C,UC}}, + [{no_calls, {C,UC}}, {no_function_calls, {LC,XC,UFC}}, {no_functions, {L,X}}, {no_inter_function_calls, EV}] = XMod#xref_mod.info, no_sum(D, C0+C, UC0+UC, LC0+LC, XC0+XC, UFC0+UFC, L0+L, X0+X, EV0+EV, NoM); no_sum([], C, UC, LC, XC, UFC, L, X, EV, NoM) -> [{no_analyzed_modules, NoM}, - {no_calls, {C,UC}}, + {no_calls, {C,UC}}, {no_function_calls, {LC,XC,UFC}}, - {no_functions, {L,X}}, + {no_functions, {L,X}}, {no_inter_function_calls, EV}]. %% -> ok | throw(Error) @@ -1712,20 +1714,20 @@ warnings(Flag, Message, [F | Fs]) -> %% pack(term()) -> term() %% %% The identify function. The returned term does not use more heap -%% than the given term. Tuples that are equal (=:=/2) are made +%% than the given term. Tuples that are equal (=:=/2) are made %% "the same". %% %% The process dictionary is used because it seems to be faster than %% anything else right now... %% %pack(T) -> T; -pack(T) -> +pack(T) -> PD = erase(), NT = pack1(T), %% true = T =:= NT, %% io:format("erasing ~p elements...~n", [length(erase())]), erase(), % wasting heap (and time)... - map(fun({K,V}) -> put(K, V) end, PD), + foreach(fun({K,V}) -> put(K, V) end, PD), NT. pack1(C) when not is_tuple(C), not is_list(C) -> diff --git a/lib/tools/src/xref_compiler.erl b/lib/tools/src/xref_compiler.erl index 67ac8c617d..1445e135be 100644 --- a/lib/tools/src/xref_compiler.erl +++ b/lib/tools/src/xref_compiler.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -31,21 +31,23 @@ -define(CALL(F), ok). -endif. +%% Avoid warning for local function error/1 clashing with autoimported BIF. +-compile({no_auto_import,[error/1]}). -export([compile/2]). -export([update_graph_counter/3]). -export([format_error/1]). --import(lists, +-import(lists, [concat/1, foldl/3, nthtail/2, reverse/1, sort/1, sublist/2]). -import(sofs, [composite/2, difference/2, empty_set/0, from_term/1, intersection/2, is_empty_set/1, multiple_relative_product/2, projection/2, relation/1, relation_to_family/1, - restriction/2, substitution/2, to_external/1, union/2, - union_of_family/1]). + restriction/2, specification/2, substitution/2, + to_external/1, union/2, union_of_family/1]). %% %% Exported functions @@ -75,7 +77,7 @@ compile(Chars, Table) -> {error, Info, Line} -> error({parse_error, Line, Info}) end. - + format_error({error, Module, Error}) -> Module:format_error(Error); format_error({parse_error, Line, Error}) -> @@ -115,7 +117,7 @@ statements([Stmt={assign, VarType, Name, E} | Stmts0], Table, L, UV) -> throw_error({variable_reassigned, xref_parser:t2s(Stmt)}); error -> {Type, OType, NewE} = t_expr(E, Table), - Val = #xref_var{name = Name, vtype = VarType, + Val = #xref_var{name = Name, vtype = VarType, otype = OType, type = Type}, NewTable = dict:store(Name, Val, Table), Stmts = if Stmts0 =:= [] -> [{variable, Name}]; true -> Stmts0 end, @@ -128,9 +130,9 @@ statements([Expr], Table, L, UV) -> E1 = un_familiarize(Type, OType, NewE), NE = case {Type, OType} of %% Edges with empty sets of line numbers are removed. - {{line, _}, edge} -> + {{line, _}, edge} -> {relation_to_family, E1}; - {_Type, edge_closure} -> + {_Type, edge_closure} -> %% Fake a closure usage, just to make sure it is destroyed. E2 = {fun graph_access/2, E1, E1}, {fun(_E) -> 'closure()' end, E2}; @@ -163,7 +165,7 @@ t_expr(E, Table) -> %%% Constant = atom() | {atom(), atom()} | MFA | {MFA, MFA} %%% Call = atom() % function in the sofs module %%% | fun() -%%% Type = {line, LineType} | function | module | application | release +%%% Type = {line, LineType} | function | module | application | release %%% | number %%% LineType = line | local_call | external_call | export_call | all_line_call %%% VarType = predef | user | tmp @@ -182,7 +184,7 @@ check_expr({variable, Name}, Table) -> case dict:find(Name, Table) of {ok, #xref_var{vtype = VarType, otype = OType, type = Type}} -> V0 = {variable, {VarType, Name}}, - V = case {VarType, Type, OType} of + V = case {VarType, Type, OType} of {predef, release, _} -> V0; {predef, application, _} -> V0; {predef, module, _} -> V0; @@ -212,7 +214,7 @@ check_expr(Expr={set, SOp, E}, Table) -> {edge_set, domain} -> vertex_set; {edge_set, weak} -> edge_set; {edge_set, strict} -> edge_set; - _ -> + _ -> throw_error({type_error, xref_parser:t2s(Expr)}) end, Op = set_op(SOp), @@ -223,10 +225,10 @@ check_expr(Expr={graph, Op, E}, Table) -> case Type of {line, _LineType} -> throw_error({type_error, xref_parser:t2s(Expr)}); - _Else -> + _Else -> ok end, - OType = + OType = case {NOType, Op} of {edge, components} -> vertex_set; {edge, condensation} -> edge_set; @@ -237,7 +239,7 @@ check_expr(Expr={graph, Op, E}, Table) -> %% Neither need nor want these ones: %% {edge_set, closure} -> edge_set_closure; %% {edge_set, components} -> vertex_set_set; - _ -> + _ -> throw_error({type_error, xref_parser:t2s(Expr)}) end, E2 = {convert, NOType, edge_closure, E1}, @@ -271,10 +273,10 @@ check_expr(Expr={set, SOp, E1, E2}, Table) -> number -> {expr, number, number, {call, ari_op(SOp), NE1, NE2}}; _Else -> % set - {Type, NewE1, NewE2} = + {Type, NewE1, NewE2} = case {type_ord(Type1), type_ord(Type2)} of {T1, T2} when T1 =:= T2 -> - %% Example: if Type1 = {line, line} and + %% Example: if Type1 = {line, line} and %% Type2 = {line, export_line}, then this is not %% correct, but works: {Type1, NE1, NE2}; @@ -296,7 +298,7 @@ check_expr(Expr={restr, ROp, E1, E2}, Table) -> throw_error({type_error, xref_parser:t2s(Expr)}); {_Type1, {line, _LineType2}} -> throw_error({type_error, xref_parser:t2s(Expr)}); - _ -> + _ -> ok end, case {OType1, OType2} of @@ -307,14 +309,14 @@ check_expr(Expr={restr, ROp, E1, E2}, Table) -> {edge, vertex} -> restriction(ROp, E1, Type1, NE1, Type2, NE2); {edge_closure, vertex} when ROp =:= '|||' -> - {expr, _, _, R1} = + {expr, _, _, R1} = closure_restriction('|', Type1, Type2, OType2, NE1, NE2), - {expr, _, _, R2} = + {expr, _, _, R2} = closure_restriction('||', Type1, Type2, OType2, NE1, NE2), {expr, Type1, edge, {call, intersection, R1, R2}}; - {edge_closure, vertex} -> + {edge_closure, vertex} -> closure_restriction(ROp, Type1, Type2, OType2, NE1, NE2); - _ -> + _ -> throw_error({type_error, xref_parser:t2s(Expr)}) end; check_expr(Expr={path, E1, E2}, Table) -> @@ -330,7 +332,7 @@ check_expr(Expr={path, E1, E2}, Table) -> end, E2b = {convert, OType2, Type2, Type1, E2a}, {OType1, NE1} = path_arg(OType1a, E1a), - NE2 = case {OType1, OType2} of + NE2 = case {OType1, OType2} of {path, edge} -> {convert, OType2, edge_closure, E2b}; {path, edge_closure} when Type1 =:= Type2 -> E2b; _ -> throw_error({type_error, xref_parser:t2s(Expr)}) @@ -347,7 +349,7 @@ check_expr({regexpr, RExpr, Type0}, _Table) -> release -> 'R' end, Var = {variable, {predef, V}}, - Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end, + Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end, {constants, RExpr}, Var}, {expr, Type, vertex, Call}; check_expr(C={constant, _Type, _OType, _C}, Table) -> @@ -368,15 +370,15 @@ check_conversion(OType, Type1, Type2, Expr) -> end. %% Allowed conversions. -conversions(_OType, {line, LineType}, {line, LineType}) -> ok; +conversions(_OType, {line, LineType}, {line, LineType}) -> ok; conversions(edge, {line, _}, {line, all_line_call}) -> ok; -conversions(edge, From, {line, Line}) +conversions(edge, From, {line, Line}) when is_atom(From), Line =/= all_line_call -> ok; conversions(vertex, From, {line, line}) when is_atom(From) -> ok; conversions(vertex, From, To) when is_atom(From), is_atom(To) -> ok; conversions(edge, From, To) when is_atom(From), is_atom(To) -> ok; %% "Extra": -conversions(edge, {line, Line}, To) +conversions(edge, {line, Line}, To) when is_atom(To), Line =/= all_line_call -> ok; conversions(vertex, {line, line}, To) when is_atom(To) -> ok; conversions(_OType, _From, _To) -> not_ok. @@ -399,7 +401,7 @@ ari_op(difference) -> fun(X, Y) -> X - Y end. restriction(ROp, E1, Type1, NE1, Type2, NE2) -> {Column, _} = restr_op(ROp), - case NE1 of + case NE1 of {call, union_of_family, _E} when ROp =:= '|' -> restriction(Column, Type1, E1, Type2, NE2); {call, union_of_family, _E} when ROp =:= '||' -> @@ -455,8 +457,8 @@ check_constants(Cs=[C={constant, Type0, OType, _Con} | Cs1], Table) -> E = function_vertices_to_family(Type, OType, {constants, S}), {expr, Type, OType, E}; [{Type1, [C1|_]}, {Type2, [C2|_]} | _] -> - throw_error({type_mismatch, - make_vertex(Type1, C1), + throw_error({type_mismatch, + make_vertex(Type1, C1), make_vertex(Type2, C2)}) end. @@ -467,7 +469,7 @@ check_mix([C={constant, Type, OType, _Con} | Cs], Type0, OType, _C0) check_mix(Cs, Type, OType, C); check_mix([C | _], _Type0, _OType0, C0) -> throw_error({type_mismatch, xref_parser:t2s(C0), xref_parser:t2s(C)}); -check_mix([], _Type0, _OType0, _C0) -> +check_mix([], _Type0, _OType0, _C0) -> ok. split(Types, Cs, Table) -> @@ -478,11 +480,11 @@ split([Type | Types], Vs, AllSoFar, _Type, Table, L) -> S0 = known_vertices(Type, Vs, Table), S = difference(S0, AllSoFar), case is_empty_set(S) of - true -> + true -> split(Types, Vs, AllSoFar, Type, Table, L); - false -> + false -> All = union(AllSoFar, S0), - split(Types, Vs, All, Type, Table, + split(Types, Vs, All, Type, Table, [{Type, to_external(S)} | L]) end; split([], Vs, All, Type, _Table, L) -> @@ -491,7 +493,7 @@ split([], Vs, All, Type, _Table, L) -> [C|_] -> throw_error({unknown_constant, make_vertex(Type, C)}) end. -make_vertex(Type, C) -> +make_vertex(Type, C) -> xref_parser:t2s({constant, Type, vertex, C}). constant_vertices([{constant, _Type, edge, {A,B}} | Cs], L) -> @@ -504,7 +506,7 @@ constant_vertices([], L) -> known_vertices('Fun', Cs, T) -> M = projection(1, Cs), F = union_of_family(restriction(fetch_value(v, T), M)), - intersection(Cs, F); + union(bifs(Cs), intersection(Cs, F)); known_vertices('Mod', Cs, T) -> intersection(Cs, fetch_value('M', T)); known_vertices('App', Cs, T) -> @@ -512,6 +514,11 @@ known_vertices('App', Cs, T) -> known_vertices('Rel', Cs, T) -> intersection(Cs, fetch_value('R', T)). +bifs(Cs) -> + specification({external, + fun({M,F,A}) -> xref_utils:is_builtin(M, F, A) end}, + Cs). + function_vertices_to_family(function, vertex, E) -> {call, partition_family, 1, E}; function_vertices_to_family(_Type, _OType, E) -> @@ -567,11 +574,11 @@ convert(E, OType, FromType, ToType) -> general(_ObjectType, FromType, ToType, X) when FromType =:= ToType -> X; -general(edge, {line, _LineType}, ToType, LEs) -> +general(edge, {line, _LineType}, ToType, LEs) -> VEs = {projection, ?Q({external, fun({V1V2,_Ls}) -> V1V2 end}), LEs}, general(edge, function, ToType, VEs); general(edge, function, ToType, VEs) -> - MEs = {projection, + MEs = {projection, ?Q({external, fun({{M1,_,_},{M2,_,_}}) -> {M1,M2} end}), VEs}, general(edge, module, ToType, MEs); @@ -580,7 +587,7 @@ general(edge, module, ToType, MEs) -> general(edge, application, ToType, AEs); general(edge, application, release, AEs) -> {image, {get, ae}, AEs}; -general(vertex, {line, _LineType}, ToType, L) -> +general(vertex, {line, _LineType}, ToType, L) -> V = {partition_family, ?Q(1), {domain, L}}, general(vertex, function, ToType, V); general(vertex, function, ToType, V) -> @@ -595,18 +602,18 @@ general(vertex, application, release, A) -> special(_ObjectType, FromType, ToType, X) when FromType =:= ToType -> X; special(edge, {line, _LineType}, {line, all_line_call}, Calls) -> - {put, ?T(mods), - {projection, - ?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}), + {put, ?T(mods), + {projection, + ?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}), Calls}, - {put, ?T(def_at), + {put, ?T(def_at), {union, {image, {get, def_at}, - {union, {domain, {get, ?T(mods)}}, + {union, {domain, {get, ?T(mods)}}, {range, {get, ?T(mods)}}}}}, {fun funs_to_lines/2, {get, ?T(def_at)}, Calls}}}; special(edge, function, {line, LineType}, VEs) -> - Var = if + Var = if LineType =:= line -> call_at; LineType =:= export_call -> e_call_at; LineType =:= local_call -> l_call_at; @@ -615,9 +622,9 @@ special(edge, function, {line, LineType}, VEs) -> line_edges(VEs, Var); special(edge, module, ToType, MEs) -> VEs = {image, - {projection, + {projection, ?Q({external, fun(FE={{M1,_,_},{M2,_,_}}) -> {{M1,M2},FE} end}), - {union, + {union, {image, {get, e}, {projection, ?Q({external, fun({M1,_M2}) -> M1 end}), MEs}}}}, MEs}, @@ -629,7 +636,7 @@ special(edge, release, ToType, REs) -> AEs = {inverse_image, {get, ae}, REs}, special(edge, application, ToType, AEs); special(vertex, function, {line, _LineType}, V) -> - {restriction, + {restriction, {union_of_family, {restriction, {get, def_at}, {domain, V}}}, {union_of_family, V}}; special(vertex, module, ToType, M) -> @@ -643,15 +650,15 @@ special(vertex, release, ToType, R) -> special(vertex, application, ToType, A). line_edges(VEs, CallAt) -> - {put, ?T(ves), VEs, - {put, ?T(m1), - {projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}), + {put, ?T(ves), VEs, + {put, ?T(m1), + {projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}), {get, ?T(ves)}}, {image, {projection, ?Q({external, fun(C={VV,_L}) -> {VV,C} end}), {union, {image, {get, CallAt}, {get, ?T(m1)}}}}, {get, ?T(ves)}}}}. -%% {(((v1,l1),(v2,l2)),l) : +%% {(((v1,l1),(v2,l2)),l) : %% (v1,l1) in DefAt and (v2,l2) in DefAt and ((v1,v2),L) in CallAt} funs_to_lines(DefAt, CallAt) -> T1 = multiple_relative_product({DefAt, DefAt}, projection(1, CallAt)), @@ -765,7 +772,7 @@ save_vars([], _D, Vs, UVs, L) -> %% Traverses the expression again, this time using more or less the %% inverse of the table created by find_nodes. The first time a node -%% is visited, its children are traversed, the following times a +%% is visited, its children are traversed, the following times a %% get instructions are inserted (using the saved value). make_instructions(N, UserVars, D) -> {D1, Is0} = make_instrs(N, D, []), @@ -777,9 +784,9 @@ make_instructions(N, UserVars, D) -> make_more_instrs([UV | UVs], D, Is) -> case dict:find(UV, D) of - error -> + error -> make_more_instrs(UVs, D, Is); - _Else -> + _Else -> {ND, NIs} = make_instrs(UV, D, Is), make_more_instrs(UVs, ND, [pop | NIs]) end; @@ -844,17 +851,17 @@ evaluate([{quote, Val} | P], T, S) -> evaluate(P, T, [Val | S]); evaluate([{get, Var} | P], T, S) when is_atom(Var) -> % predefined Value = fetch_value(Var, T), - Val = case Value of + Val = case Value of {R, _} -> R; % relation _ -> Value % simple set end, - evaluate(P, T, [Val | S]); + evaluate(P, T, [Val | S]); evaluate([{get, {inverse, Var}} | P], T, S) -> % predefined, inverse {_, R} = fetch_value(Var, T), - evaluate(P, T, [R | S]); + evaluate(P, T, [R | S]); evaluate([{get, {user, Var}} | P], T, S) -> Val = fetch_value(Var, T), - evaluate(P, T, [Val | S]); + evaluate(P, T, [Val | S]); evaluate([{get, Var} | P], T, S) -> % tmp evaluate(P, T, [dict:fetch(Var, T) | S]); evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) -> @@ -862,7 +869,7 @@ evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) -> evaluate(P, dict:store(Var, Val, T1), S); evaluate([{save, {user, Name}} | P], T, S=[Val | _]) -> #xref_var{vtype = user, otype = OType, type = Type} = dict:fetch(Name, T), - NewVar = #xref_var{name = Name, value = Val, + NewVar = #xref_var{name = Name, value = Val, vtype = user, otype = OType, type = Type}, T1 = update_graph_counter(Val, +1, T), NT = dict:store(Name, NewVar, T1), @@ -889,7 +896,7 @@ update_graph_counter(Value, Inc, T) -> error when Inc =:= 1 -> dict:store(Value, 1, T) end; - _EXIT -> + _EXIT -> T end. diff --git a/lib/tools/src/xref_parser.yrl b/lib/tools/src/xref_parser.yrl index e23dce1dec..1279ece061 100644 --- a/lib/tools/src/xref_parser.yrl +++ b/lib/tools/src/xref_parser.yrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% @@ -169,12 +169,11 @@ is_prefix_op('#') -> numeric; is_prefix_op(_) -> false. check_regexp(String) -> - case regexp:parse(String) of + case re:compile(String) of {ok, _Expr} -> {regexpr, String}; - {error, Reason} -> - F = regexp:format_error(Reason), - return_error(0, ["invalid_regexp", String, F]) + {error, {ErrString, Position}} -> + return_error(Position, ["invalid_regexp", String, ErrString]) end. check_regexp_variable('_') -> diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl index db755c31d8..d22f0df164 100644 --- a/lib/tools/src/xref_reader.erl +++ b/lib/tools/src/xref_reader.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(xref_reader). @@ -22,7 +22,7 @@ -import(lists, [keysearch/3, member/2, reverse/1]). --record(xrefr, +-record(xrefr, {module=[], function=[], def_at=[], @@ -59,15 +59,15 @@ module(Module, Forms, CollectBuiltins, X, DF) -> Attrs = [{Attr,V} || {attribute,_Line,Attr,V} <- Forms], IsAbstract = xref_utils:is_abstract_module(Attrs), - S = #xrefr{module = Module, builtins_too = CollectBuiltins, + S = #xrefr{module = Module, builtins_too = CollectBuiltins, is_abstr = IsAbstract, x = X, df = DF}, forms(Forms, S). forms([F | Fs], S) -> S1 = form(F, S), forms(Fs, S1); -forms([], S) -> - #xrefr{module = M, def_at = DefAt, +forms([], S) -> + #xrefr{module = M, def_at = DefAt, l_call_at = LCallAt, x_call_at = XCallAt, el = LC, ex = XC, x = X, df = Depr, lattrs = AL, xattrs = AX, battrs = B, unresolved = U} = S, @@ -75,7 +75,7 @@ forms([], S) -> {ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr}, U}. form({attribute, Line, xref, Calls}, S) -> % experimental - #xrefr{module = M, function = Fun, + #xrefr{module = M, function = Fun, lattrs = L, xattrs = X, battrs = B} = S, attr(Calls, Line, M, Fun, L, X, B, S); form({attribute, _Line, _Attr, _Val}, S) -> @@ -110,12 +110,12 @@ clauses([{clause, _Line, _H, G, B} | Cs], FunVars, Matches, S) -> S2 = expr(B, S1), S3 = S2#xrefr{funvars = FunVars, matches = Matches}, clauses(Cs, S3); -clauses([], _FunVars, _Matches, S) -> +clauses([], _FunVars, _Matches, S) -> S. attr([E={From, To} | As], Ln, M, Fun, AL, AX, B, S) -> case mfa(From, M) of - {_, _, MFA} when MFA =:= Fun; [] =:= Fun -> + {_, _, MFA} when MFA =:= Fun; [] =:= Fun -> attr(From, To, Ln, M, Fun, AL, AX, B, S, As, E); {_, _, _} -> attr(As, Ln, M, Fun, AL, AX, [E | B], S); @@ -164,7 +164,7 @@ expr({call, Line, %% Added in R10B-6. M:F/A. expr({'fun', Line, {function, Mod, Fun, Arity}}, S); expr({'fun', Line, {function, Mod, Name, Arity}}, S) -> - %% Added in R10B-6. M:F/A. + %% Added in R10B-6. M:F/A. As = lists:duplicate(Arity, {atom, Line, foo}), external_call(Mod, Name, As, Line, false, S); expr({'fun', Line, {function, Name, Arity}, _Extra}, S) -> @@ -183,7 +183,7 @@ expr({call, Line, {remote, _Line, Mod, Name}, As}, S) -> expr({call, Line, F, As}, S) -> external_call(erlang, apply, [F, list2term(As)], Line, true, S); expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}, _Extra}}, S) -> - %% This is what is needed in R7 to avoid warnings for the functions + %% This is what is needed in R7 to avoid warnings for the functions %% that are passed around by the "expansion" of list comprehension. S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]}, clauses(Cs, S1); @@ -192,6 +192,14 @@ expr({match, _Line, {var,_,Var}, E}, S) -> %% Args = [A,B], apply(m, f, Args) S1 = S#xrefr{matches = [{Var, E} | S#xrefr.matches]}, expr(E, S1); +expr({op, _Line, 'orelse', Op1, Op2}, S) -> + expr([Op1, Op2], S); +expr({op, _Line, 'andalso', Op1, Op2}, S) -> + expr([Op1, Op2], S); +expr({op, Line, Op, Operand1, Operand2}, S) -> + external_call(erlang, Op, [Operand1, Operand2], Line, false, S); +expr({op, Line, Op, Operand}, S) -> + external_call(erlang, Op, [Operand], Line, false, S); expr(T, S) when is_tuple(T) -> expr(tuple_to_list(T), S); expr([E | Es], S) -> @@ -241,13 +249,13 @@ external_call(Mod, Fun, ArgsList, Line, X, S) -> _Else -> % apply2, 1 or 2 check_funarg(W, ArgsList, Line, S1) end. - + eval_args(Mod, Fun, ArgsTerm, Line, S, ArgsList, Extra) -> {IsSimpleCall, M, F} = mod_fun(Mod, Fun), case term2list(ArgsTerm, [], S) of undefined -> S1 = unresolved(M, F, -1, Line, S), - expr(ArgsList, S1); + expr(ArgsList, S1); ArgsList2 when not IsSimpleCall -> S1 = unresolved(M, F, length(ArgsList2), Line, S), expr(ArgsList, S1); @@ -288,14 +296,14 @@ fun_args(apply2, [FunArg, Args]) -> {FunArg, Args}; fun_args(1, [FunArg | Args]) -> {FunArg, Args}; fun_args(2, [_Node, FunArg | Args]) -> {FunArg, Args}. -list2term([A | As]) -> +list2term([A | As]) -> {cons, 0, A, list2term(As)}; -list2term([]) -> +list2term([]) -> {nil, 0}. term2list({cons, _Line, H, T}, L, S) -> term2list(T, [H | L], S); -term2list({nil, _Line}, L, _S) -> +term2list({nil, _Line}, L, _S) -> reverse(L); term2list({var, _, Var}, L, S) -> case keysearch(Var, 1, S#xrefr.matches) of @@ -332,11 +340,11 @@ handle_call(Locality, To0, Line, S, IsUnres) -> true -> S end, - case Locality of - local -> + case Locality of + local -> S1#xrefr{el = [Call | S1#xrefr.el], l_call_at = [CallAt | S1#xrefr.l_call_at]}; - external -> + external -> S1#xrefr{ex = [Call | S1#xrefr.ex], x_call_at = [CallAt | S1#xrefr.x_call_at]} end. diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl index 680b7e8aac..9d4a175d88 100644 --- a/lib/tools/src/xref_utils.erl +++ b/lib/tools/src/xref_utils.erl @@ -18,6 +18,8 @@ %% -module(xref_utils). +%% Avoid warning for local function error/1 clashing with autoimported BIF. +-compile({no_auto_import,[error/1]}). -export([xset/2]). -export([is_directory/1, file_info/1, fa_to_mfa/2]). @@ -640,22 +642,22 @@ neighbours([], G, Fun, VT, L, _V, Vs) -> neighbours(Vs, G, Fun, VT, L). match_list(L, RExpr) -> - {ok, Expr} = regexp:parse(RExpr), + {ok, Expr} = re:compile(RExpr), filter(fun(E) -> match(E, Expr) end, L). match_one(VarL, Con, Col) -> select_each(VarL, fun(E) -> Con =:= element(Col, E) end). match_many(VarL, RExpr, Col) -> - {ok, Expr} = regexp:parse(RExpr), + {ok, Expr} = re:compile(RExpr), select_each(VarL, fun(E) -> match(element(Col, E), Expr) end). match(I, Expr) when is_integer(I) -> S = integer_to_list(I), - {match, 1, length(S)} =:= regexp:first_match(S, Expr); + {match, [{0,length(S)}]} =:= re:run(S, Expr, [{capture, first}]); match(A, Expr) when is_atom(A) -> S = atom_to_list(A), - {match, 1, length(S)} =:= regexp:first_match(S, Expr). + {match, [{0,length(S)}]} =:= re:run(S, Expr, [{capture, first}]). select_each([{Mod,Funs} | L], Pred) -> case filter(Pred, Funs) of diff --git a/lib/tools/test/Makefile b/lib/tools/test/Makefile index 3a59be758a..826a8f3ee8 100644 --- a/lib/tools/test/Makefile +++ b/lib/tools/test/Makefile @@ -39,7 +39,8 @@ INSTALL_PROGS= $(TARGET_FILES) EMAKEFILE=Emakefile -SPEC_FILES= tools.spec tools.spec.win +SPEC_FILES= tools.spec +COVER_FILE = tools.cover # ---------------------------------------------------- # Release directory specification @@ -84,7 +85,8 @@ release_spec: opt release_tests_spec: make_emakefile $(INSTALL_DIR) $(RELSYSDIR) - $(INSTALL_DATA) $(SPEC_FILES) $(EMAKEFILE) $(ERL_FILES) $(RELSYSDIR) + $(INSTALL_DATA) $(SPEC_FILES) $(COVER_FILE) $(EMAKEFILE) \ + $(ERL_FILES) $(RELSYSDIR) chmod -f -R u+w $(RELSYSDIR) @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index b9ccd62d0b..494ef55f59 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% Copyright Ericsson AB 2001-2010. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -18,13 +18,16 @@ %% -module(cover_SUITE). --export([all/1]). +-export([all/0, init_per_testcase/2, end_per_testcase/2, + suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2]). + -export([start/1, compile/1, analyse/1, misc/1, stop/1, distribution/1, export_import/1, otp_5031/1, eif/1, otp_5305/1, otp_5418/1, otp_6115/1, otp_7095/1, otp_8188/1, otp_8270/1, otp_8273/1, otp_8340/1]). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). %%---------------------------------------------------------------------- %% The following directory structure is assumed: @@ -37,18 +40,50 @@ %% y %%---------------------------------------------------------------------- -all(suite) -> +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> case whereis(cover_server) of undefined -> [start, compile, analyse, misc, stop, distribution, - export_import, - otp_5031, eif, otp_5305, otp_5418, otp_6115, otp_7095, - otp_8188, otp_8270, otp_8273, otp_8340]; + export_import, otp_5031, eif, otp_5305, otp_5418, + otp_6115, otp_7095, otp_8188, otp_8270, otp_8273, + otp_8340]; _pid -> - {skip,"It looks like the test server is running cover. " - "Can't run cover test."} + {skip, + "It looks like the test server is running " + "cover. Can't run cover test."} end. +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(TC, Config) when TC =:= misc; TC =:= compile -> + case code:which(crypto) of + Path when is_list(Path) -> + init_per_testcase(dummy_tc, Config); + _Else -> + {skip, "No crypto file to test with"} + end; +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + %cover:stop(), + ok. + start(suite) -> []; start(Config) when is_list(Config) -> ?line ok = file:set_cwd(?config(data_dir, Config)), @@ -381,8 +416,8 @@ export_import(Config) when is_list(Config) -> ?line {ok,a} = cover:compile(a), ?line ?t:capture_start(), ?line ok = cover:export("all_exported"), - ?line [Text2] = ?t:capture_get(), - ?line "Export includes data from imported files"++_ = lists:flatten(Text2), + ?line [] = ?t:capture_get(), +% ?line "Export includes data from imported files"++_ = lists:flatten(Text2), ?line ?t:capture_stop(), ?line ok = cover:stop(), ?line ok = cover:import("all_exported"), diff --git a/lib/tools/test/cprof_SUITE.erl b/lib/tools/test/cprof_SUITE.erl index e697cc1571..b6f786d33f 100644 --- a/lib/tools/test/cprof_SUITE.erl +++ b/lib/tools/test/cprof_SUITE.erl @@ -41,7 +41,7 @@ -define(config(A,B),config(A,B)). -export([config/2]). -else. --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). -endif. -ifdef(debug). @@ -63,14 +63,17 @@ config(data_dir, _) -> "cprof_SUITE_data". -else. %% When run in test server. --export([all/1, init_per_testcase/2, fin_per_testcase/2, not_run/1]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2, + init_per_testcase/2, end_per_testcase/2, + not_run/1]). -export([basic/1, on_load/1, modules/1]). init_per_testcase(_Case, Config) -> ?line Dog=test_server:timetrap(test_server:seconds(30)), [{watchdog, Dog}|Config]. -fin_per_testcase(_Case, Config) -> +end_per_testcase(_Case, Config) -> erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_count]), erlang:trace_pattern(on_load, false, [local,meta,call_count]), erlang:trace(all, false, [all]), @@ -78,16 +81,30 @@ fin_per_testcase(_Case, Config) -> test_server:timetrap_cancel(Dog), ok. -all(doc) -> - ["Test the cprof profiling tool."]; -all(suite) -> - case test_server:is_native(?MODULE) of +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + case test_server:is_native(cprof_SUITE) of true -> [not_run]; false -> [basic, on_load, modules] -%, on_and_off, info, -% pause_and_restart, combo] end. +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + not_run(Config) when is_list(Config) -> {skipped,"Native code"}. diff --git a/lib/tools/test/emem_SUITE.erl b/lib/tools/test/emem_SUITE.erl index 430fa86c6c..8b38e7d3a8 100644 --- a/lib/tools/test/emem_SUITE.erl +++ b/lib/tools/test/emem_SUITE.erl @@ -24,7 +24,8 @@ receive_and_save_trace/2, send_trace/2]). --export([all/1, init_per_testcase/2, fin_per_testcase/2]). +-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). -export([live_node/1, 'sparc_sunos5.8_32b_emt2.0'/1, @@ -41,7 +42,7 @@ -include_lib("kernel/include/file.hrl"). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). -define(DEFAULT_TIMEOUT, ?t:minutes(5)). @@ -65,23 +66,32 @@ %% %% -all(doc) -> []; -all(suite) -> +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> case is_debug_compiled() of - true -> {skipped, "Not run when debug compiled"}; + true -> {skip, "Not run when debug compiled"}; false -> test_cases() end. - -test_cases() -> - [live_node, - 'sparc_sunos5.8_32b_emt2.0', + +groups() -> + []. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +test_cases() -> + [live_node, 'sparc_sunos5.8_32b_emt2.0', 'pc_win2000_32b_emt2.0', 'pc.smp_linux2.2.19pre17_32b_emt2.0', 'powerpc_darwin7.7.0_32b_emt2.0', 'alpha_osf1v5.1_64b_emt2.0', 'sparc_sunos5.8_64b_emt2.0', - 'sparc_sunos5.8_32b_emt1.0', - 'pc_win2000_32b_emt1.0', + 'sparc_sunos5.8_32b_emt1.0', 'pc_win2000_32b_emt1.0', 'powerpc_darwin7.7.0_32b_emt1.0', 'alpha_osf1v5.1_64b_emt1.0', 'sparc_sunos5.8_64b_emt1.0']. @@ -100,7 +110,7 @@ init_per_testcase(Case, Config) when is_list(Config) -> [{watchdog, Dog}, {testcase, Case} | Config]) end. -fin_per_testcase(_Case, Config) when is_list(Config) -> +end_per_testcase(_Case, Config) when is_list(Config) -> ignore_cores:restore(Config), Dog = ?config(watchdog, Config), ?t:timetrap_cancel(Dog), @@ -700,8 +710,8 @@ start_node(Name, Args) -> % stop_node(Node) -> % ?t:stop_node(Node). -is_debug_compiled() -> - is_debug_compiled(erlang:system_info(system_version)). +is_debug_compiled() -> +is_debug_compiled(erlang:system_info(system_version)). is_debug_compiled([$d,$e,$b,$u,$g | _]) -> true; diff --git a/lib/tools/test/eprof_SUITE.erl b/lib/tools/test/eprof_SUITE.erl index 028fea8fe1..16246d5e0b 100644 --- a/lib/tools/test/eprof_SUITE.erl +++ b/lib/tools/test/eprof_SUITE.erl @@ -18,12 +18,111 @@ %% -module(eprof_SUITE). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). --export([all/1,tiny/1,eed/1]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2,tiny/1,eed/1,basic/1]). -all(suite) -> [tiny,eed]. +suite() -> [{ct_hooks,[ts_install_cth]}]. +all() -> + [basic, tiny, eed]. + +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +basic(suite) -> []; +basic(Config) when is_list(Config) -> + + %% load eprof_test and change directory + + ?line {ok, OldCurDir} = file:get_cwd(), + Datadir = ?config(data_dir, Config), + Privdir = ?config(priv_dir, Config), + ?line {ok,eprof_test} = compile:file(filename:join(Datadir, "eprof_test"), + [trace,{outdir, Privdir}]), + ?line ok = file:set_cwd(Privdir), + ?line code:purge(eprof_test), + ?line {module,eprof_test} = code:load_file(eprof_test), + + %% rootset profiling + + ?line ensure_eprof_stopped(), + ?line profiling = eprof:profile([self()]), + ?line {error, already_profiling} = eprof:profile([self()]), + ?line profiling_stopped = eprof:stop_profiling(), + ?line profiling_already_stopped = eprof:stop_profiling(), + ?line profiling = eprof:start_profiling([self(),self(),self()]), + ?line profiling_stopped = eprof:stop_profiling(), + + %% with patterns + + ?line profiling = eprof:start_profiling([self()], {?MODULE, '_', '_'}), + ?line {error, already_profiling} = eprof:start_profiling([self()], {?MODULE, '_', '_'}), + ?line profiling_stopped = eprof:stop_profiling(), + ?line profiling = eprof:start_profiling([self()], {?MODULE, start_stop, '_'}), + ?line profiling_stopped = eprof:stop_profiling(), + ?line profiling = eprof:start_profiling([self()], {?MODULE, start_stop, 1}), + ?line profiling_stopped = eprof:stop_profiling(), + + %% with fun + + ?line {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end), + ?line profiling = eprof:profile([self()]), + ?line {error, already_profiling} = eprof:profile(fun() -> eprof_test:go(10) end), + ?line profiling_stopped = eprof:stop_profiling(), + ?line {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end), + ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end), + ?line Pid = whereis(eprof), + ?line {ok, _} = eprof:profile(erlang:processes() -- [Pid], fun() -> eprof_test:go(10) end), + ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}), + ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, '_'}), + ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, 1}), + ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, dec, 1}), + + %% error case + + ?line error = eprof:profile([Pid], fun() -> eprof_test:go(10) end), + ?line Pid = whereis(eprof), + ?line error = eprof:profile([Pid], fun() -> eprof_test:go(10) end), + ?line A = spawn(fun() -> receive _ -> ok end end), + ?line profiling = eprof:profile([A]), + ?line true = exit(A, kill_it), + ?line profiling_stopped = eprof:stop_profiling(), + ?line {error,_} = eprof:profile(fun() -> a = b end), + + %% with mfa + + ?line {ok, _} = eprof:profile([], eprof_test, go, [10]), + ?line {ok, _} = eprof:profile([], eprof_test, go, [10], {eprof_test, dec, 1}), + + %% dump + + ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}), + ?line [{_, Mfas}] = eprof:dump(), + ?line Dec_mfa = {eprof_test, dec, 1}, + ?line Go_mfa = {eprof_test, go, 1}, + ?line {value, {Go_mfa, { 1, _Time1}}} = lists:keysearch(Go_mfa, 1, Mfas), + ?line {value, {Dec_mfa, {11, _Time2}}} = lists:keysearch(Dec_mfa, 1, Mfas), + + %% change current working directory + + ?line ok = file:set_cwd(OldCurDir), + ?line stopped = eprof:stop(), + ok. tiny(suite) -> []; tiny(Config) when is_list(Config) -> @@ -40,11 +139,11 @@ tiny(Config) when is_list(Config) -> ?line code:purge(eprof_suite_test), ?line {module,eprof_suite_test} = code:load_file(eprof_suite_test), ?line {ok,_Pid} = eprof:start(), - ?line nothing_to_analyse = eprof:analyse(), - ?line nothing_to_analyse = eprof:total_analyse(), + ?line nothing_to_analyze = eprof:analyze(), + ?line nothing_to_analyze = eprof:analyze(total), ?line eprof:profile([], eprof_suite_test, test, [Config]), - ?line ok = eprof:analyse(), - ?line ok = eprof:total_analyse(), + ?line ok = eprof:analyze(), + ?line ok = eprof:analyze(total), ?line ok = eprof:log("eprof_SUITE_logfile"), ?line stopped = eprof:stop(), ?line ?t:timetrap_cancel(TTrap), @@ -79,8 +178,8 @@ eed(Config) when is_list(Config) -> ?line {ok,ok} = eprof:profile([], eed, file, [Script]), ?line {T3,_} = statistics(runtime), ?line profiling_already_stopped = eprof:stop_profiling(), - ?line ok = eprof:analyse(), - ?line ok = eprof:total_analyse(), + ?line ok = eprof:analyze(), + ?line ok = eprof:analyze(total), ?line ok = eprof:log("eprof_SUITE_logfile"), ?line stopped = eprof:stop(), ?line ?t:timetrap_cancel(TTrap), diff --git a/lib/tools/test/eprof_SUITE_data/eprof_test.erl b/lib/tools/test/eprof_SUITE_data/eprof_test.erl new file mode 100644 index 0000000000..33c428e893 --- /dev/null +++ b/lib/tools/test/eprof_SUITE_data/eprof_test.erl @@ -0,0 +1,9 @@ +-module(eprof_test). +-export([go/1]). + +go(N) -> + 0 = dec(N), + ok. + +dec(0) -> 0; +dec(N) -> dec(N - 1). diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl index e437007e76..78de77526c 100644 --- a/lib/tools/test/fprof_SUITE.erl +++ b/lib/tools/test/fprof_SUITE.erl @@ -18,10 +18,11 @@ %% -module(fprof_SUITE). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). %% Test server framework exports --export([all/1, not_run/1]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2, not_run/1]). %% Test suites -export([stack_seq/1, tail_seq/1, create_file_slow/1, spawn_simple/1, @@ -54,18 +55,33 @@ -all(doc) -> - ["Test the 'fprof' profiling tool."]; -all(suite) -> - case test_server:is_native(?MODULE) of - true -> - [not_run]; +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + case test_server:is_native(fprof_SUITE) of + true -> [not_run]; false -> [stack_seq, tail_seq, create_file_slow, spawn_simple, imm_tail_seq, imm_create_file_slow, imm_compile, cpu_create_file_slow] end. +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + not_run(Config) when is_list(Config) -> {skipped, "Native code"}. @@ -356,7 +372,7 @@ imm_tail_seq(Config) when is_list(Config) -> ?line profiling_stopped = eprof:stop_profiling(), ?line R2 = R0, %% - ?line eprof:analyse(), + ?line eprof:analyze(), ?line stopped = eprof:stop(), %% ?line {ok, Tracer} = fprof:profile(start), @@ -471,7 +487,7 @@ imm_compile(Config) when is_list(Config) -> ?line TS3 = erlang:now(), ?line profiling_stopped = eprof:stop_profiling(), %% - ?line eprof:analyse(), + ?line eprof:analyze(), ?line stopped = eprof:stop(), %% ?line {ok, Tracer} = fprof:profile(start), diff --git a/lib/tools/test/ignore_cores.erl b/lib/tools/test/ignore_cores.erl index 8902a469ef..8b1ac0fe6c 120000..100644 --- a/lib/tools/test/ignore_cores.erl +++ b/lib/tools/test/ignore_cores.erl @@ -1 +1,158 @@ -../../../erts/test/ignore_cores.erl
\ No newline at end of file +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File : ignore_cores.erl +%%% Author : Rickard Green <[email protected]> +%%% Description : +%%% +%%% Created : 11 Feb 2008 by Rickard Green <[email protected]> +%%%------------------------------------------------------------------- + +-module(ignore_cores). + +-include_lib("test_server/include/test_server.hrl"). + +-export([init/1, fini/1, setup/3, setup/4, restore/1, dir/1]). + +-record(ignore_cores, {org_cwd, + org_path, + org_pwd_env, + ign_dir = false, + cores_dir = false}). + +%% +%% Takes a testcase config +%% + +init(Config) -> + {ok, OrgCWD} = file:get_cwd(), + [{ignore_cores, + #ignore_cores{org_cwd = OrgCWD, + org_path = code:get_path(), + org_pwd_env = os:getenv("PWD")}} + | lists:keydelete(ignore_cores, 1, Config)]. + +fini(Config) -> + #ignore_cores{org_cwd = OrgCWD, + org_path = OrgPath, + org_pwd_env = OrgPWD} = ?config(ignore_cores, Config), + ok = file:set_cwd(OrgCWD), + true = code:set_path(OrgPath), + case OrgPWD of + false -> ok; + _ -> true = os:putenv("PWD", OrgPWD) + end, + lists:keydelete(ignore_cores, 1, Config). + +setup(Suite, Testcase, Config) -> + setup(Suite, Testcase, Config, false). + +setup(Suite, Testcase, Config, SetCwd) when is_atom(Suite), + is_atom(Testcase), + is_list(Config) -> + #ignore_cores{org_cwd = OrgCWD, + org_path = OrgPath, + org_pwd_env = OrgPWD} = ?config(ignore_cores, Config), + Path = lists:map(fun (".") -> OrgCWD; (Dir) -> Dir end, OrgPath), + true = code:set_path(Path), + PrivDir = ?config(priv_dir, Config), + IgnDir = filename:join([PrivDir, + atom_to_list(Suite) + ++ "_" + ++ atom_to_list(Testcase) + ++ "_wd"]), + ok = file:make_dir(IgnDir), + case SetCwd of + false -> + ok; + _ -> + ok = file:set_cwd(IgnDir), + OrgPWD = case os:getenv("PWD") of + false -> false; + PWD -> + os:putenv("PWD", IgnDir), + PWD + end + end, + ok = file:write_file(filename:join([IgnDir, "ignore_core_files"]), <<>>), + %% cores are dumped in /cores on MacOS X + CoresDir = case {?t:os_type(), filelib:is_dir("/cores")} of + {{unix,darwin}, true} -> + filelib:fold_files("/cores", + "^core.*$", + false, + fun (C,Cs) -> [C|Cs] end, + []); + _ -> + false + end, + lists:keyreplace(ignore_cores, + 1, + Config, + {ignore_cores, + #ignore_cores{org_cwd = OrgCWD, + org_path = OrgPath, + org_pwd_env = OrgPWD, + ign_dir = IgnDir, + cores_dir = CoresDir}}). + +restore(Config) -> + #ignore_cores{org_cwd = OrgCWD, + org_path = OrgPath, + org_pwd_env = OrgPWD, + ign_dir = IgnDir, + cores_dir = CoresDir} = ?config(ignore_cores, Config), + try + case CoresDir of + false -> + ok; + _ -> + %% Move cores dumped by these testcases in /cores + %% to cwd. + lists:foreach(fun (C) -> + case lists:member(C, CoresDir) of + true -> ok; + _ -> + Dst = filename:join( + [IgnDir, + filename:basename(C)]), + {ok, _} = file:copy(C, Dst), + file:delete(C) + end + end, + filelib:fold_files("/cores", + "^core.*$", + false, + fun (C,Cs) -> [C|Cs] end, + [])) + end + after + catch file:set_cwd(OrgCWD), + catch code:set_path(OrgPath), + case OrgPWD of + false -> ok; + _ -> catch os:putenv("PWD", OrgPWD) + end + end. + + +dir(Config) -> + #ignore_cores{ign_dir = Dir} = ?config(ignore_cores, Config), + Dir. diff --git a/lib/tools/test/instrument_SUITE.erl b/lib/tools/test/instrument_SUITE.erl index da5930e015..6800a94f94 100644 --- a/lib/tools/test/instrument_SUITE.erl +++ b/lib/tools/test/instrument_SUITE.erl @@ -18,22 +18,43 @@ %% -module(instrument_SUITE). --export([all/1,init_per_testcase/2,fin_per_testcase/2]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2, + init_per_testcase/2,end_per_testcase/2]). -export(['+Mim true'/1, '+Mis true'/1]). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). init_per_testcase(_Case, Config) -> ?line Dog=?t:timetrap(10000), [{watchdog, Dog}|Config]. -fin_per_testcase(_Case, Config) -> +end_per_testcase(_Case, Config) -> Dog=?config(watchdog, Config), ?t:timetrap_cancel(Dog), ok. -all(suite) -> ['+Mim true', '+Mis true']. +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + ['+Mim true', '+Mis true']. + +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + '+Mim true'(doc) -> ["Check that memory data can be read and processed"]; '+Mim true'(suite) -> []; diff --git a/lib/tools/test/lcnt_SUITE.erl b/lib/tools/test/lcnt_SUITE.erl index e6866f721d..21383fa544 100644 --- a/lib/tools/test/lcnt_SUITE.erl +++ b/lib/tools/test/lcnt_SUITE.erl @@ -18,10 +18,10 @@ %% -module(lcnt_SUITE). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). %% Test server specific exports --export([all/1]). +-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2]). -export([init_per_suite/1, end_per_suite/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -51,10 +51,21 @@ end_per_testcase(_Case, Config) -> ?t:timetrap_cancel(Dog), ok. -all(suite) -> - % Test cases +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> [load_v1, conflicts, locations, swap_keys]. +groups() -> + []. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + %%---------------------------------------------------------------------- %% Tests %%---------------------------------------------------------------------- diff --git a/lib/tools/test/make_SUITE.erl b/lib/tools/test/make_SUITE.erl index 72dccdb465..524ed04af4 100644 --- a/lib/tools/test/make_SUITE.erl +++ b/lib/tools/test/make_SUITE.erl @@ -18,12 +18,13 @@ %% -module(make_SUITE). --export([all/1, make_all/1, make_files/1]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2, make_all/1, make_files/1]). -export([otp_6057_init/1, otp_6057_a/1, otp_6057_b/1, otp_6057_c/1, otp_6057_end/1]). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). -include_lib("kernel/include/file.hrl"). @@ -35,9 +36,27 @@ %% that the file :"test5.erl" shall be compiled with the 'S' option, %% i.e. produce "test5.S" instead of "test5.<objext>" -all(suite) -> [make_all, make_files, - {conf, otp_6057_init, - [otp_6057_a,otp_6057_b,otp_6057_c], otp_6057_end}]. +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [make_all, make_files, {group, otp_6057}]. + +groups() -> + [{otp_6057,[],[otp_6057_a, otp_6057_b, + otp_6057_c]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + otp_6057_init(Config). + +end_per_group(_GroupName, Config) -> + otp_6057_end(Config). + test_files() -> ["test1", "test2", "test3", "test4"]. @@ -146,7 +165,7 @@ otp_6057_init(Config) when is_list(Config) -> otp_6057_a(suite) -> []; otp_6057_a(doc) -> - ["Test that make:all/0 looks for object file in correct place"]; + ["Test that make:all/0, suite/0 looks for object file in correct place"]; otp_6057_a(Config) when is_list(Config) -> ?line PrivDir = ?config(priv_dir, Config), diff --git a/lib/tools/test/tools.cover b/lib/tools/test/tools.cover new file mode 100644 index 0000000000..1053be4f0f --- /dev/null +++ b/lib/tools/test/tools.cover @@ -0,0 +1,2 @@ +{incl_app,tools,details}. + diff --git a/lib/tools/test/tools.spec b/lib/tools/test/tools.spec index 93d5930472..1b07cf1cb6 100644 --- a/lib/tools/test/tools.spec +++ b/lib/tools/test/tools.spec @@ -1 +1 @@ -{topcase, {dir, "../tools_test"}}. +{suites,"../tools_test",all}. diff --git a/lib/tools/test/tools_SUITE.erl b/lib/tools/test/tools_SUITE.erl index 6b952f10ab..69dfab8fe7 100644 --- a/lib/tools/test/tools_SUITE.erl +++ b/lib/tools/test/tools_SUITE.erl @@ -18,28 +18,45 @@ %% -module(tools_SUITE). --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). %% Default timetrap timeout (set in init_per_testcase). -define(default_timeout, ?t:minutes(1)). -define(application, tools). %% Test server specific exports --export([all/1]). --export([init_per_testcase/2, fin_per_testcase/2]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2]). +-export([init_per_testcase/2, end_per_testcase/2]). %% Test cases must be exported. -export([app_test/1]). -all(doc) -> - []; -all(suite) -> +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> [app_test]. +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + init_per_testcase(_Case, Config) -> ?line Dog=test_server:timetrap(?default_timeout), [{watchdog, Dog}|Config]. -fin_per_testcase(_Case, Config) -> +end_per_testcase(_Case, Config) -> Dog=?config(watchdog, Config), test_server:timetrap_cancel(Dog), ok. diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl index b4684140ca..1fad070b67 100644 --- a/lib/tools/test/xref_SUITE.erl +++ b/lib/tools/test/xref_SUITE.erl @@ -29,28 +29,29 @@ -define(privdir, "xref_SUITE_priv"). -define(copydir, "xref_SUITE_priv/datacopy"). -else. --include("test_server.hrl"). +-include_lib("test_server/include/test_server.hrl"). -define(format(S, A), ok). -define(datadir, ?config(data_dir, Conf)). -define(privdir, ?config(priv_dir, Conf)). -define(copydir, ?config(copy_dir, Conf)). -endif. --export([all/1, init/1, fini/1]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2, init/1, fini/1]). --export([xref/1, - addrem/1, convert/1, intergraph/1, lines/1, loops/1, +-export([ + addrem/1, convert/1, intergraph/1, lines/1, loops/1, no_data/1, modules/1]). --export([files/1, - add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1, +-export([ + add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1, replace/1, update/1, deprecated/1, trycatch/1, abstract_modules/1, fun_mfa/1, qlc/1]). --export([analyses/1, +-export([ analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1]). --export([misc/1, +-export([ format_error/1, otp_7423/1, otp_7831/1]). -import(lists, [append/2, flatten/1, keysearch/3, member/2, sort/1, usort/1]). @@ -59,7 +60,7 @@ range/1, relation_to_family/1, set/1, to_external/1, union/2]). --export([init_per_testcase/2, fin_per_testcase/2]). +-export([init_per_testcase/2, end_per_testcase/2]). %% Checks some info counters of a server and some relations that should hold. -export([check_count/1, check_state/1]). @@ -68,8 +69,36 @@ -include_lib("tools/src/xref.hrl"). -all(suite) -> - {conf, init, [xref, files, analyses, misc], fini}. +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [{group, xref}, {group, files}, {group, analyses}, + {group, misc}]. + +groups() -> + [{xref, [], + [addrem, convert, intergraph, lines, loops, no_data, + modules]}, + {files, [], + [add, default, info, lib, read, read2, remove, replace, + update, deprecated, trycatch, abstract_modules, fun_mfa, + qlc]}, + {analyses, [], + [analyze, basic, md, q, variables, unused_locals]}, + {misc, [], [format_error, otp_7423, otp_7831]}]. + +init_per_suite(Config) -> + init(Config). + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + init(Conf) when is_list(Conf) -> DataDir = ?datadir, @@ -82,7 +111,7 @@ init(Conf) when is_list(Conf) -> ?line ok = erl_tar:extract(TarFile, [compressed]), ?line ok = file:delete(TarFile), [{copy_dir, CopyDir} | Conf]. - + fini(Conf) when is_list(Conf) -> %% Nothing. Conf. @@ -91,13 +120,11 @@ init_per_testcase(_Case, Config) -> Dog=?t:timetrap(?t:minutes(2)), [{watchdog, Dog}|Config]. -fin_per_testcase(_Case, _Config) -> +end_per_testcase(_Case, _Config) -> Dog=?config(watchdog, _Config), test_server:timetrap_cancel(Dog), ok. -xref(suite) -> - [addrem, convert, intergraph, lines, loops, no_data, modules]. %% Seems a bit short... addrem(suite) -> []; @@ -120,7 +147,7 @@ addrem(Conf) when is_list(Conf) -> LCallAt_m1 = [], XCallAt_m1 = [{E1,13}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), D2 = {F2,7}, @@ -132,7 +159,7 @@ addrem(Conf) when is_list(Conf) -> LCallAt_m2 = [], XCallAt_m2 = [{E2,96}], Info2 = #xref_mod{name = m2, app_name = [a2]}, - ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, + ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, XC_m2, LC_m2), ?line S5 = set_up(S2), @@ -142,7 +169,7 @@ addrem(Conf) when is_list(Conf) -> ?line {ok, XMod2, S6a} = remove_module(S6, m2), ?line [a2] = XMod2#xref_mod.app_name, ?line S7 = set_up(S6a), - + ?line AppInfo1 = #xref_app{name = a1, rel_name = [r1]}, ?line S9 = add_application(S7, AppInfo1), ?line S10 = set_up(S9), @@ -186,7 +213,7 @@ convert(Conf) when is_list(Conf) -> LCallAt_m1 = [], XCallAt_m1 = [{E1,13},{E2,17},{E4,7}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), D2 = {F2,7}, @@ -200,7 +227,7 @@ convert(Conf) when is_list(Conf) -> LCallAt_m2 = [], XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}], Info2 = #xref_mod{name = m2, app_name = [a2]}, - ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, + ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, XC_m2, LC_m2), D4 = {F4,6}, @@ -213,7 +240,7 @@ convert(Conf) when is_list(Conf) -> LCallAt_m3 = [{E5,19}], XCallAt_m3 = [{UE2,22}], Info3 = #xref_mod{name = m3, app_name = [a3]}, - ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3, + ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3, XC_m3, LC_m3), Info4 = #xref_mod{name = m4, app_name = [a2]}, @@ -303,7 +330,7 @@ convert(Conf) when is_list(Conf) -> ?line {ok, _} = eval("(XXL) (Lin) (Fun) E", AllCallAt, S), ?line {ok, _} = eval("(XXL) (XXL) (Lin) (Fun) E", AllCallAt, S), - ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]), + ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]), [{{D1,D3},[13]}, {{D7,D4},[12]}], S), ?line {ok, _} = eval(f("(Fun) ~p", [AllMs]), AllE, S), ?line {ok, _} = eval("(Fun) [m1->m2,m2->m3]", [E1,E2,E6], S), @@ -323,7 +350,7 @@ intergraph(Conf) when is_list(Conf) -> F3 = {m1,f3,3}, F4 = {m1,f4,4}, F5 = {m1,f5,5}, - + F6 = {m2,f1,6}, % X F7 = {m2,f1,7}, F8 = {m2,f1,8}, @@ -339,7 +366,7 @@ intergraph(Conf) when is_list(Conf) -> E5 = {F4,F2}, E6 = {F5,F4}, E7 = {F4,F5}, - + E8 = {F6,F7}, E9 = {F7,F8}, E10 = {F8,F1}, % X @@ -363,9 +390,9 @@ intergraph(Conf) when is_list(Conf) -> LCallAt_m1 = [{E1,1},{E2,2},{E3,3},{E5,5},{E6,6},{E7,7}], XCallAt_m1 = [{E1,4}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), - + D6 = {F6,6}, D7 = {F7,7}, D8 = {F8,8}, @@ -380,7 +407,7 @@ intergraph(Conf) when is_list(Conf) -> LCallAt_m2 = [{E8,8},{E9,9},{E11,11},{E12,12},{E13,13},{E14,14}], XCallAt_m2 = [{E10,10},{E15,15}], Info2 = #xref_mod{name = m2, app_name = [a2]}, - ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, + ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, XC_m2, LC_m2), AppInfo1 = #xref_app{name = a1, rel_name = [r1]}, @@ -397,13 +424,13 @@ intergraph(Conf) when is_list(Conf) -> ?line {ok, _} = eval("EE | m2", [{F6,F1}], S), ?line {ok, _} = eval("EE | m2 + EE | m2", [{F6,F1}], S), - ?line {ok, _} = eval("(Fun)(Lin)(E | m1)", + ?line {ok, _} = eval("(Fun)(Lin)(E | m1)", to_external(union(set(XC_m1), set(LC_m1))), S), - ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)", - [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}], + ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)", + [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}], S), ?line {ok, _} = eval("(XXL)(ELin)(EE | m2)", [{{D6,D1},[8,11,12]}], S), - ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)", + ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)", [{{D6,D1},[8,11,12]}], S), %% Combining graphs (equal or different): @@ -420,15 +447,15 @@ intergraph(Conf) when is_list(Conf) -> ?line {ok, _} = eval("EE | m1 + E | m1", LC_m1, S), ?line {ok, _} = eval(f("EE | ~p + E | ~p", [F2, F2]), [E1,E2], S), %% [1,4] from 'calls' is a subset of [1,2,4] from Inter Call Graph: - ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]), + ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]), [{{D2,D1},[1,4]},{{D2,D3},[2]}], S), - ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]), + ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]), [{{D2,D1},[1,2,4]}], S), ?line {ok, _} = eval(f("(XXL)((ELin)(EE | ~p) + (Lin)(E | ~p))", [F2, F2]), [{{D2,D1},[1,2,4]},{{D2,D3},[2]}], S), - ?line {ok, _} = - eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]), + ?line {ok, _} = + eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]), [{{D2,D1},[1,2,4]}], S), ?line {ok, _} = eval(f("(Fun)(Lin) ~p", [{F2, F1}]), [E1], S), %% The external call E4 is included in the reply: @@ -438,7 +465,7 @@ intergraph(Conf) when is_list(Conf) -> %% The local call E1 is included in the reply: ?line {ok, _} = eval("(XXL)(Lin)(XC | m1)", [{{D2,D1},[1,4]}], S), - ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)", + ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)", [F2, F1, F2, F1]), [{E4,[1,4]}], S), ?line {ok, _} = eval("# (ELin) E", 6, S), @@ -449,7 +476,7 @@ lines(suite) -> []; lines(doc) -> ["More test of Inter Call Graph, and regular expressions"]; lines(Conf) when is_list(Conf) -> S0 = new(), - + F1 = {m1,f1,1}, % X F2 = {m1,f2,2}, F3 = {m1,f3,3}, @@ -464,14 +491,14 @@ lines(Conf) when is_list(Conf) -> E5 = {F2,F4}, % X E6 = {F5,F6}, E7 = {F6,F4}, % X - + D1 = {F1,1}, D2 = {F2,2}, D3 = {F3,3}, D4 = {F4,4}, D5 = {F5,5}, D6 = {F6,6}, - + DefAt_m1 = [D1,D2,D3,D5,D6], X_m1 = [F1,F5], % L_m1 = [F2,F3,F6], @@ -480,7 +507,7 @@ lines(Conf) when is_list(Conf) -> LCallAt_m1 = [{E1,1},{E3,3},{E6,6}], XCallAt_m1 = [{E2,2},{E4,4},{E5,5},{E7,7}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), DefAt_m2 = [D4], @@ -491,9 +518,9 @@ lines(Conf) when is_list(Conf) -> LCallAt_m2 = [], XCallAt_m2 = [], Info2 = #xref_mod{name = m2, app_name = [a2]}, - ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, + ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, XC_m2, LC_m2), - + AppInfo1 = #xref_app{name = a1, rel_name = [r1]}, ?line S5 = add_application(S2, AppInfo1), AppInfo2 = #xref_app{name = a2, rel_name = [r1]}, @@ -509,10 +536,10 @@ lines(Conf) when is_list(Conf) -> {{D5,D4},[6]}], S), ?line {ok, _} = eval("(XXL)(Lin) (E | m1)", [{{D1,D2},[1]},{{D1,D4},[4]},{{D2,D1},[2]}, - {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}], + {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}], S), ?line {ok, _} = eval("(E | m1) + (EE | m1)", - [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}], + [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}], S), ?line {ok, _} = eval("(Lin)(E | m1)", [{E4,[4]},{E1,[1]},{E2,[2]},{E5,[5]}, @@ -567,7 +594,7 @@ lines(Conf) when is_list(Conf) -> loops(suite) -> []; loops(doc) -> ["More Inter Call Graph, loops and \"unusual\" cases"]; loops(Conf) when is_list(Conf) -> - S0 = new(), + S0 = new(), F1 = {m1,f1,1}, % X F2 = {m1,f2,2}, @@ -582,7 +609,7 @@ loops(Conf) when is_list(Conf) -> E3 = {F3,F4}, E4 = {F4,F5}, E5 = {F5,F3}, % X - + D1 = {F1,1}, D2 = {F2,2}, D3 = {F3,3}, @@ -598,7 +625,7 @@ loops(Conf) when is_list(Conf) -> LCallAt_m1 = [{E2,2},{E3,3},{E4,4}], XCallAt_m1 = [{E1,1},{E5,5}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), ?line S = set_up(S1), @@ -659,16 +686,16 @@ modules(Conf) when is_list(Conf) -> ?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]), ?line {ok, S0} = xref_base:new([{xref_mode, modules}]), - ?line {ok, release2, S1} = + ?line {ok, release2, S1} = xref_base:add_release(S0, Dir, [{name,release2}]), ?line S = set_up(S1), ?line {{error, _, {unavailable_analysis, undefined_function_calls}}, _} = xref_base:analyze(S, undefined_function_calls), - ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} = + ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} = xref_base:analyze(S, locals_not_used), - ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} = + ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} = xref_base:analyze(S, {call, foo}), - ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} = + ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} = xref_base:analyze(S, {use, foo}), ?line analyze(undefined_functions, [{x,undef,0}], S), ?line 5 = length(xref_base:info(S)), @@ -680,9 +707,6 @@ modules(Conf) when is_list(Conf) -> ?line ok = xref_base:delete(S), ok. -files(suite) -> - [add, default, info, lib, read, read2, remove, replace, update, - deprecated, trycatch, abstract_modules, fun_mfa, qlc]. add(suite) -> []; add(doc) -> ["Add modules, applications, releases, directories"]; @@ -708,7 +732,7 @@ add(Conf) when is_list(Conf) -> {unix, _} -> ?line make_udir(UDir), ?line make_ufile(UFile); - _ -> + _ -> true end, @@ -743,20 +767,20 @@ add(Conf) when is_list(Conf) -> xref_base:add_release(S, foo, [{builtins,not_a_value}]), ?line {error, _, {invalid_filename,{foo,bar}}} = xref_base:add_release(S, {foo,bar}, []), - ?line {ok, S1} = + ?line {ok, S1} = xref_base:set_default(S, [{verbose,false}, {warnings, false}]), ?line case os:type() of {unix, _} -> - ?line {error, _, {file_error, _, _}} = + ?line {error, _, {file_error, _, _}} = xref_base:add_release(S, UDir); _ -> true end, - ?line {error, _, {file_error, _, _}} = + ?line {error, _, {file_error, _, _}} = xref_base:add_release(S, fname(["/a/b/c/d/e/f","__foo"])), - ?line {ok, release2, S2} = + ?line {ok, release2, S2} = xref_base:add_release(S1, Dir, [{name,release2}]), - ?line {error, _, {module_clash, {x, _, _}}} = + ?line {error, _, {module_clash, {x, _, _}}} = xref_base:add_module(S2, Xbeam), ?line {ok, S3} = xref_base:remove_release(S2, release2), ?line {ok, rel2, S4} = xref_base:add_release(S3, Dir), @@ -764,11 +788,11 @@ add(Conf) when is_list(Conf) -> xref_base:add_release(S4, Dir), ?line {ok, S5} = xref_base:remove_release(S4, rel2), %% One unreadable file and one JAM file found (no verification here): - ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"), + ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"), [{recurse,true}, {warnings,true}]), ?line case os:type() of {unix, _} -> - ?line {error, _, {file_error, _, _}} = + ?line {error, _, {file_error, _, _}} = xref_base:add_directory(S6, UDir); _ -> true @@ -803,7 +827,7 @@ default(Conf) when is_list(Conf) -> xref_base:set_default(S, [not_an_option]), ?line D = xref_base:get_default(S), - ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] = + ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] = D, ?line ok = xref_base:delete(S), @@ -831,7 +855,7 @@ info(Conf) when is_list(Conf) -> ?line {error, _, {no_such_info, release}} = xref:info(s, release), ?line {error, _, {no_such_info, release}} = xref:info(s, release, rel), ?line {error, _, {no_such_module, mod}} = xref:info(s, modules, mod), - ?line {error, _, {no_such_application, app}} = + ?line {error, _, {no_such_application, app}} = xref:info(s, applications, app), ?line {error, _, {no_such_release, rel}} = xref:info(s, releases, rel), ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]), @@ -845,9 +869,9 @@ info(Conf) when is_list(Conf) -> ?line [{rel2,_}] = xref:info(s, releases, rel2), ?line {error, _, {no_such_library, foo}} = xref:info(s, libraries, [foo]), - ?line {ok, lib1} = + ?line {ok, lib1} = compile:file(fname(LDir,lib1),[debug_info,{outdir,LDir}]), - ?line {ok, lib2} = + ?line {ok, lib2} = compile:file(fname(LDir,lib2),[debug_info,{outdir,LDir}]), ?line ok = xref:set_library_path(s, [LDir], [{verbose,false}]), ?line [{lib1,_}, {lib2, _}] = xref:info(s, libraries), @@ -883,13 +907,13 @@ lib(Conf) when is_list(Conf) -> xref:set_library_path(s, ["foo"], [not_an_option]), ?line {error, _, {invalid_path,otp}} = xref:set_library_path(s,otp), ?line {error, _, {invalid_path,[""]}} = xref:set_library_path(s,[""]), - ?line {error, _, {invalid_path,[[$a | $b]]}} = + ?line {error, _, {invalid_path,[[$a | $b]]}} = xref:set_library_path(s,[[$a | $b]]), ?line {error, _, {invalid_path,[otp]}} = xref:set_library_path(s,[otp]), ?line {ok, []} = xref:get_library_path(s), ?line ok = xref:set_library_path(s, [Dir], [{verbose,false}]), ?line {ok, UnknownFunctions} = xref:q(s, "U"), - ?line [{lib1,unknown,0}, {lib2,local,0}, + ?line [{lib1,unknown,0}, {lib2,local,0}, {lib2,unknown,0}, {unknown,unknown,0}] = UnknownFunctions, ?line {ok, [{lib2,f,0},{lib3,f,0}]} = xref:q(s, "DF"), @@ -934,7 +958,7 @@ lib(Conf) when is_list(Conf) -> ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]), ?line {ok, cp} = xref:add_module(s, fname(Dir,"cp.beam")), ?line {ok, [{lists, sort, 1}]} = xref:q(s, "U"), - ?line ok = xref:set_library_path(s, code_path), + ?line ok = xref:set_library_path(s, code_path), ?line {ok, []} = xref:q(s, "U"), ?line check_state(s), ?line xref:stop(s), @@ -1010,18 +1034,18 @@ do_read(File, Version) -> ?line {ok, CallsB} = xref:q(s, "(Lin) (E - UC) "), ?line ok = check_state(s), ?line {ok, XU} = xref:q(s, "XU"), - ?line Erl = set([{erlang,length,1},{erlang,integer,1}, + ?line Erl = set([{erlang,length,1},{erlang,integer,1}, {erlang,binary_to_term,1}]), - ?line [{erlang,binary_to_term,1},{erlang,length,1}] = + ?line [{erlang,binary_to_term,1},{erlang,length,1}] = to_external(intersection(set(XU), Erl)), - ?line xref:stop(s). + ?line xref:stop(s). %% What is expected when xref_SUITE_data/read/read.erl is added: read_expected(Version) -> %% Line positions in xref_SUITE_data/read/read.erl: - POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10, - POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8, - POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10, + POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10, + POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8, + POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10, POS14 = POS13+18, % POS15 = POS14+23, FF = {read,funfuns,0}, @@ -1162,7 +1186,7 @@ read_expected(Version) -> {POS14+17,{{read,bi,0},{read,bi,0}}}], OK = case Version of - abstract_v1 -> + abstract_v1 -> [{POS8+3, {FF,{erlang,apply,3}}}, {POS10+1, {FF,{erlang,apply,3}}}, {POS10+6, {FF,{erlang,apply,3}}}] @@ -1170,7 +1194,7 @@ read_expected(Version) -> [{0,{FF,{read,'$F_EXPR',178}}}, {0,{FF,{modul,'$F_EXPR',179}}}] ++ O1; - _ -> + _ -> % [{POS15+2,{{read,bi,0},{foo,t,0}}}, % {POS15+3,{{read,bi,0},{bar,t,0}}}, % {POS15+6,{{read,bi,0},{read,local,0}}}, @@ -1183,18 +1207,34 @@ read_expected(Version) -> end, %% When builtins =:= true: - OKB = [{POS13+1,{FF,{erts_debug,apply,4}}}, - {POS13+2,{FF,{erts_debug,apply,4}}}, - {POS13+3,{FF,{erts_debug,apply,4}}}, - {POS1+3, {FF,{erlang,binary_to_term,1}}}, - {POS3+1, {FF,{erlang,spawn,3}}}, - {POS3+2, {FF,{erlang,spawn,3}}}, - {POS3+3, {FF,{erlang,spawn_link,3}}}, - {POS3+4, {FF,{erlang,spawn_link,3}}}, - {POS6+4, {FF,{erlang,spawn,3}}}, - {POS13+5, {{read,bi,0},{erlang,length,1}}}, - {POS14+3, {{read,bi,0},{erlang,length,1}}}] - ++ OK, + OKB1 = [{POS13+1,{FF,{erts_debug,apply,4}}}, + {POS13+2,{FF,{erts_debug,apply,4}}}, + {POS13+3,{FF,{erts_debug,apply,4}}}, + {POS1+3, {FF,{erlang,binary_to_term,1}}}, + {POS3+1, {FF,{erlang,spawn,3}}}, + {POS3+2, {FF,{erlang,spawn,3}}}, + {POS3+3, {FF,{erlang,spawn_link,3}}}, + {POS3+4, {FF,{erlang,spawn_link,3}}}, + {POS6+4, {FF,{erlang,spawn,3}}}, + {POS13+5, {{read,bi,0},{erlang,length,1}}}, + {POS14+3, {{read,bi,0},{erlang,length,1}}}], + + %% Operators (OTP-8647): + OKB = case Version of + abstract_v1 -> + []; + _ -> + [{POS13+16, {{read,bi,0},{erlang,'!',2}}}, + {POS13+16, {{read,bi,0},{erlang,'-',1}}}, + {POS13+16, {{read,bi,0},{erlang,self,0}}}] + end + ++ [{POS14+19, {{read,bi,0},{erlang,'+',2}}}, + {POS14+21, {{read,bi,0},{erlang,'+',2}}}, + {POS13+16, {{read,bi,0},{erlang,'==',2}}}, + {POS14+15, {{read,bi,0},{erlang,'==',2}}}, + {POS13+5, {{read,bi,0},{erlang,'>',2}}}, + {POS14+3, {{read,bi,0},{erlang,'>',2}}}] + ++ OKB1 ++ OK, {U, OK, OKB}. @@ -1217,9 +1257,9 @@ read2(Conf) when is_list(Conf) -> spawn_opt(fun() -> foo end, [link]), spawn_opt(f(), {read2,f}, [{min_heap_size,1000}]), - spawn_opt(f(), + spawn_opt(f(), fun() -> f() end, [flopp]), - spawn_opt(f(), + spawn_opt(f(), read2, f, [], []); f() -> %% Duplicated unresolved calls are ignored: @@ -1237,7 +1277,7 @@ read2(Conf) when is_list(Conf) -> ?line {ok, U2} = xref:q(s, "(Lin) UC"), ?line {ok, OK2} = xref:q(s, "(Lin) (E - UC)"), ?line true = U =:= U2, - ?line true = OK =:= OK2, + ?line true = OK =:= OK2, ?line ok = check_state(s), ?line xref:stop(s), @@ -1304,7 +1344,7 @@ replace(Conf) when is_list(Conf) -> ?line {ok, true} = xref:set_default(s, warnings, false), ?line {ok, rel2} = xref:add_release(s, Dir, []), ?line {error, _, _} = xref:replace_application(s, app1, "no_data"), - ?line {error, _, {no_such_application, app12}} = + ?line {error, _, {no_such_application, app12}} = xref:replace_application(s, app12, A1_0, []), ?line {error, _, {invalid_filename,{foo,bar}}} = xref:replace_application(s, app1, {foo,bar}, []), @@ -1312,7 +1352,7 @@ replace(Conf) when is_list(Conf) -> xref:replace_application(s, foo, bar, [not_an_option]), ?line {error, _, {invalid_options,[{builtins,not_a_value}]}} = xref:replace_application(s, foo, bar, [{builtins,not_a_value}]), - ?line {ok, app1} = + ?line {ok, app1} = xref:replace_application(s, app1, A1_0), ?line [{_, AppInfo}] = xref:info(s, applications, app1), ?line {value, {release, [rel2]}} = keysearch(release, 1, AppInfo), @@ -1332,14 +1372,14 @@ replace(Conf) when is_list(Conf) -> ?line {ok, x} = compile:file(X, [no_debug_info, {outdir,EB1_1}]), ?line {error, _, {no_debug_info, _}} = xref:replace_module(s, x, Xbeam), - ?line {error, _, {module_mismatch, x,y}} = + ?line {error, _, {module_mismatch, x,y}} = xref:replace_module(s, x, Ybeam), ?line case os:type() of {unix, _} -> ?line hide_file(Ybeam), - ?line {error, _, {file_error, _, _}} = + ?line {error, _, {file_error, _, _}} = xref:replace_module(s, x, Ybeam); - _ -> + _ -> true end, ?line ok = xref:remove_module(s, x), @@ -1362,16 +1402,16 @@ update(Conf) when is_list(Conf) -> Source = fname(Dir, "x.erl"), Beam = fname(Dir, "x.beam"), ?line copy_file(fname(Dir, "x.erl.1"), Source), - ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]), - + ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]), + ?line {ok, _} = start(s), - ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]), + ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]), ?line {ok, [x]} = xref:add_directory(s, Dir, [{builtins,true}]), ?line {error, _, {invalid_options,[not_an_option]}} = xref:update(s, [not_an_option]), ?line {ok, []} = xref:update(s), ?line {ok, [{erlang,atom_to_list,1}]} = xref:q(s, "XU"), - + ?line [{x, ModInfo}] = xref:info(s, modules, x), ?line case keysearch(directory, 1, ModInfo) of {value, {directory, Dir}} -> ok @@ -1379,7 +1419,7 @@ update(Conf) when is_list(Conf) -> timer:sleep(2000), % make sure modification time has changed ?line copy_file(fname(Dir, "x.erl.2"), Source), - ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]), + ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]), ?line {ok, [x]} = xref:update(s, []), ?line {ok, [{erlang,list_to_atom,1}]} = xref:q(s, "XU"), @@ -1454,11 +1494,11 @@ deprecated(Conf) when is_list(Conf) -> DF = usort(DF_3++[{{M9,t,0},{M9,f,1}}]), ?line {ok,DF} = xref:analyze(s, deprecated_function_calls), - ?line {ok,DF_1} = + ?line {ok,DF_1} = xref:analyze(s, {deprecated_function_calls,next_version}), - ?line {ok,DF_2} = + ?line {ok,DF_2} = xref:analyze(s, {deprecated_function_calls,next_major_release}), - ?line {ok,DF_3} = + ?line {ok,DF_3} = xref:analyze(s, {deprecated_function_calls,eventually}), D = to_external(range(from_term(DF))), @@ -1467,11 +1507,11 @@ deprecated(Conf) when is_list(Conf) -> D_3 = to_external(range(from_term(DF_3))), ?line {ok,D} = xref:analyze(s, deprecated_functions), - ?line {ok,D_1} = + ?line {ok,D_1} = xref:analyze(s, {deprecated_functions,next_version}), - ?line {ok,D_2} = + ?line {ok,D_2} = xref:analyze(s, {deprecated_functions,next_major_release}), - ?line {ok,D_3} = + ?line {ok,D_3} = xref:analyze(s, {deprecated_functions,eventually}), ?line ok = check_state(s), @@ -1516,11 +1556,11 @@ deprecated(Conf) when is_list(Conf) -> DFa = DFa_3, ?line {ok,DFa} = xref:analyze(s, deprecated_function_calls), - ?line {ok,DFa_1} = + ?line {ok,DFa_1} = xref:analyze(s, {deprecated_function_calls,next_version}), - ?line {ok,DFa_2} = + ?line {ok,DFa_2} = xref:analyze(s, {deprecated_function_calls,next_major_release}), - ?line {ok,DFa_3} = + ?line {ok,DFa_3} = xref:analyze(s, {deprecated_function_calls,eventually}), ?line ok = check_state(s), @@ -1564,11 +1604,11 @@ deprecated(Conf) when is_list(Conf) -> DFb = usort(DFb_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}}]), ?line {ok,DFb} = xref:analyze(s, deprecated_function_calls), - ?line {ok,DFb_1} = + ?line {ok,DFb_1} = xref:analyze(s, {deprecated_function_calls,next_version}), - ?line {ok,DFb_2} = + ?line {ok,DFb_2} = xref:analyze(s, {deprecated_function_calls,next_major_release}), - ?line {ok,DFb_3} = + ?line {ok,DFb_3} = xref:analyze(s, {deprecated_function_calls,eventually}), ?line ok = check_state(s), @@ -1599,7 +1639,7 @@ trycatch(Conf) when is_list(Conf) -> catch error:a -> err:e1(); error:b -> err:e2() - after + after fini:shed() end. ">>, @@ -1616,7 +1656,7 @@ trycatch(Conf) when is_list(Conf) -> {{{A,A,0},{err,e2,0}},[13]}, {{{A,A,0},{fini,shed,0}},[15]}, {{{A,A,0},{foo,bar,0}},[7]}, - {{{A,A,0},{foo,foo,0}},[9]}]} = + {{{A,A,0},{foo,foo,0}},[9]}]} = xref:q(s, "(Lin) (E | trycatch:trycatch/0)"), ?line ok = check_state(s), @@ -1662,7 +1702,7 @@ abstract_modules(Conf) when is_list(Conf) -> {{{A,args,1},{A,local,1}},[6]}, {{{A,args,1},{A,new,2}},[8]}, {{{A,local,1},{A,module_info,1}},[12]}, - {{{param,new,2},{param,instance,2}},[0]}]} = + {{{param,new,2},{param,instance,2}},[0]}]} = xref:q(s, "(Lin) E"), ?line {ok,[{param,args,1}, {param,instance,2}, @@ -1747,10 +1787,10 @@ qlc(Conf) when is_list(Conf) -> t() -> dets:open_file(t, []), dets:insert(t, [{1,a},{2,b},{3,c},{4,d}]), - MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y} + MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y} end), QH1 = dets:table(t, [{traverse, {select, MS}}]), - QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t), + QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t), (X > 1) or (X < 5)]), true = qlc:info(QH1) =:= qlc:info(QH2), dets:close(t), @@ -1772,8 +1812,6 @@ qlc(Conf) when is_list(Conf) -> ok. -analyses(suite) -> - [analyze, basic, md, q, variables, unused_locals]. analyze(suite) -> []; analyze(doc) -> ["Simple analyses"]; @@ -1783,7 +1821,7 @@ analyze(Conf) when is_list(Conf) -> xref_base:analyze(S0, undefined_function_calls, [not_an_option]), ?line {{error, _, {invalid_query,{q}}}, _} = xref_base:q(S0,{q}), ?line {{error, _, {unknown_analysis,foo}}, _} = xref_base:analyze(S0, foo), - ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} = + ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} = xref_base:analyze(S0, {use,{foo,bar,-1}}), CopyDir = ?copydir, @@ -1803,30 +1841,30 @@ analyze(Conf) when is_list(Conf) -> ?line {ok, rel2, S1} = xref_base:add_release(S0, Dir, [{verbose,false}]), ?line S = set_up(S1), - ?line {ok, _} = + ?line {ok, _} = analyze(undefined_function_calls, [{{x,xx,0},{x,undef,0}}], S), ?line {ok, _} = analyze(undefined_functions, [{x,undef,0}], S), ?line {ok, _} = analyze(locals_not_used, [{x,l,0},{x,l1,0}], S), ?line {ok, _} = analyze(exports_not_used, [{x,xx,0},{y,t,0}], S), - ?line {ok, _} = + ?line {ok, _} = analyze(deprecated_function_calls, [{{y,t,0},{x,t,0}}], S), ?line {ok, _} = analyze({deprecated_function_calls,next_version}, [], S), - ?line {ok, _} = + ?line {ok, _} = analyze({deprecated_function_calls,next_major_release}, [], S), - ?line {ok, _} = analyze({deprecated_function_calls,eventually}, + ?line {ok, _} = analyze({deprecated_function_calls,eventually}, [{{y,t,0},{x,t,0}}], S), ?line {ok, _} = analyze(deprecated_functions, [{x,t,0}], S), ?line {ok, _} = analyze({deprecated_functions,next_version}, [], S), - ?line {ok, _} = + ?line {ok, _} = analyze({deprecated_functions,next_major_release}, [], S), ?line {ok, _} = analyze({deprecated_functions,eventually}, [{x,t,0}], S), ?line {ok, _} = analyze({call, {x,xx,0}}, [{x,undef,0}], S), - ?line {ok, _} = + ?line {ok, _} = analyze({call, [{x,xx,0},{x,l,0}]}, [{x,l1,0},{x,undef,0}], S), ?line {ok, _} = analyze({use, {x,l,0}}, [{x,l1,0}], S), - ?line {ok, _} = + ?line {ok, _} = analyze({use, [{x,l,0},{x,l1,0}]}, [{x,l,0},{x,l1,0}], S), ?line {ok, _} = analyze({module_call, x}, [x], S), @@ -1881,7 +1919,7 @@ basic(Conf) when is_list(Conf) -> LCallAt_m1 = [{E7,12}], XCallAt_m1 = [{E1,13},{E2,17},{E4,7}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), D2 = {F2,7}, @@ -1895,7 +1933,7 @@ basic(Conf) when is_list(Conf) -> LCallAt_m2 = [], XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}], Info2 = #xref_mod{name = m2, app_name = [a2]}, - ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, + ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, XC_m2, LC_m2), D4 = {F4,6}, @@ -1908,7 +1946,7 @@ basic(Conf) when is_list(Conf) -> LCallAt_m3 = [{E5,19}], XCallAt_m3 = [{UE2,22}], Info3 = #xref_mod{name = m3, app_name = [a3]}, - ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3, + ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3, XC_m3, LC_m3), Info4 = #xref_mod{name = m4, app_name = [a2]}, @@ -1955,7 +1993,7 @@ basic(Conf) when is_list(Conf) -> ?line {ok, _} = eval(f("(Mod) ~p", [[F1,F6,F5]]), [m1,m3], S), ?line {ok, _} = eval("(Lin) M - (Lin) m1", [{F2,7},{F3,9},{F7,19},{F4,6},{F5,97},{UF2,0}], S), - ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]), + ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]), [{F1,12},{F6,3}], S), ?line {ok, _} = eval(f("X * ~p", [[F1, F2, F3, F4, F5]]), [F3, F4], S), @@ -1976,7 +2014,7 @@ basic(Conf) when is_list(Conf) -> ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]), [{{D1,D3},[13]},{{D1,D4},[7]}],S), ?line {ok, _} = eval(f("XC | (~p + ~p)", [F1, F2]), [E1,E4,E3,UE1], S), - ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]), + ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]), [{{D1,D3},[13]},{{D1,D4},[7]}], S), ?line {ok, _} = eval("LC | m3", [E5], S), ?line {ok, _} = eval(f("LC | ~p", [F1]), [E7], S), @@ -1984,7 +2022,7 @@ basic(Conf) when is_list(Conf) -> ?line {ok, _} = eval("E | m1", [E1,E2,E4,E7], S), ?line {ok, _} = eval(f("E | ~p", [F1]), [E1,E7,E4], S), ?line {ok, _} = eval(f("E | (~p + ~p)", [F1, F2]), [E1,E7,E4,E3,UE1], S), - + ?line {ok, _} = eval("XC || m1", [E3,UE2], S), ?line {ok, _} = eval(f("XC || ~p", [F6]), [E3], S), ?line {ok, _} = eval(f("XC || (~p + ~p)", [F4, UF2]), [UE1,E4,E6], S), @@ -2012,18 +2050,18 @@ basic(Conf) when is_list(Conf) -> ?line {ok, _} = eval("components V", type_error, S), ?line {ok, _} = eval("components E + components E", type_error, S), - ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]), + ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]), [F6,F3,F7,F4,F5,UF1,UF2], S), - ?line {ok, _} = + ?line {ok, _} = eval(f("domain (closure E || ~p)", [[UF2,F7]]), [F1,F2,F6], S), ?line {ok, _} = eval("components E", [], S), ?line {ok, _} = eval("components (Mod) E", [[m1,m2,m3]], S), ?line {ok, _} = eval("components closure (Mod) E", [[m1,m2,m3]], S), - ?line {ok, _} = eval("condensation (Mod) E", + ?line {ok, _} = eval("condensation (Mod) E", [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S), - ?line {ok, _} = eval("condensation closure (Mod) E", + ?line {ok, _} = eval("condensation closure (Mod) E", [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S), - ?line {ok, _} = eval("condensation closure closure closure (Mod) E", + ?line {ok, _} = eval("condensation closure closure closure (Mod) E", [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S), ?line {ok, _} = eval("weak condensation (Mod) E", [{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]},{[m17],[m17]}], S), @@ -2035,11 +2073,11 @@ basic(Conf) when is_list(Conf) -> [[m1,m2,m3]], S), %% |, ||, ||| - ?line {ok, _} = eval("(Lin) E || V", type_error, S), - ?line {ok, _} = eval("E ||| (Lin) V", type_error, S), + ?line {ok, _} = eval("(Lin) E || V", type_error, S), + ?line {ok, _} = eval("E ||| (Lin) V", type_error, S), ?line {ok, _} = eval("E ||| m1", [E7], S), ?line {ok, _} = eval("closure E ||| m1", [E7,{F1,UF1},{F6,UF1}], S), - ?line {ok, _} = eval("closure E ||| [m1,m2]", + ?line {ok, _} = eval("closure E ||| [m1,m2]", [{F1,UF1},{F2,F7},{F1,F7},{F6,UF1},{F2,UF1},{F7,UF1},E7,E1,E2,E3], S), ?line {ok, _} = eval("AE | a1", [{a1,a1},{a1,a2},{a1,a3}], S), @@ -2095,7 +2133,7 @@ md(Conf) when is_list(Conf) -> Y = fname(Dir, "y__y.erl"), Xbeam = fname(Dir, "x__x.beam"), Ybeam = fname(Dir, "y__y.beam"), - + ?line {error, _, {invalid_filename,{foo,bar}}} = xref:m({foo,bar}), ?line {error, _, {invalid_filename,{foo,bar}}} = xref:d({foo,bar}), @@ -2171,7 +2209,7 @@ variables(Conf) when is_list(Conf) -> LCallAt_m1 = [], XCallAt_m1 = [{E1,13},{E3,17}], Info1 = #xref_mod{name = m1, app_name = [a1]}, - ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, + ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1, XC_m1, LC_m1), D2 = {F2,7}, @@ -2183,11 +2221,11 @@ variables(Conf) when is_list(Conf) -> LCallAt_m2 = [], XCallAt_m2 = [{E2,96}], Info2 = #xref_mod{name = m2, app_name = [a2]}, - ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, + ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2, XC_m2, LC_m2), ?line S = set_up(S2), - + ?line eval("T1=E, T2=E*T1, T3 = T2*T2, T4=range T3, T5=T3|T4, T5", [E1,E2,E3], S), ?line eval("((E*E)*(E*E)) | (range ((E*E)*(E*E)))", @@ -2202,16 +2240,16 @@ variables(Conf) when is_list(Conf) -> ?line {ok, S102} = eval("T2 := E | m2", [E2], S101), ?line {{ok, [{user, ['T0', 'T1', 'T2']}]}, _} = xref_base:variables(S102), ?line {ok, S103} = xref_base:forget(S102, 'T0'), - ?line {{ok, [{user, ['T1', 'T2']}]}, S104} = + ?line {{ok, [{user, ['T1', 'T2']}]}, S104} = xref_base:variables(S103, [user]), ?line {ok, S105} = xref_base:forget(S104), ?line {{ok, [{user, []}]}, S106} = xref_base:variables(S105), - ?line {{ok, [{predefined,_}]}, S107_0} = + ?line {{ok, [{predefined,_}]}, S107_0} = xref_base:variables(S106, [predefined]), - ?line {ok, S107_1} = + ?line {ok, S107_1} = eval("TT := E, TT2 := V, TT1 := TT * TT", [E1,E2,E3], S107_0), - ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} = + ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} = xref_base:variables(S107_1), ?line {ok, S107} = xref_base:forget(S107_1), @@ -2220,14 +2258,14 @@ variables(Conf) when is_list(Conf) -> Beam = fname(Dir, "lib1.beam"), ?line copy_file(fname(Dir, "lib1.erl"), Beam), - ?line {ok, S108} = + ?line {ok, S108} = xref_base:set_library_path(S107, [Dir], [{verbose,false}]), ?line {{error, _, _}, _} = xref_base:variables(S108, [{verbose,false}]), ?line {ok, S109} = xref_base:set_library_path(S108, [], [{verbose,false}]), ?line Tabs = length(ets:all()), - ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus", + ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus", 'closure()', S109), ?line {{ok, [{user, ['Eplus','TT']}]}, S111} = xref_base:variables(S110), ?line {ok, S112} = xref_base:forget(S111, ['TT','Eplus']), @@ -2289,25 +2327,23 @@ unused_locals(Conf) when is_list(Conf) -> ?line {ok, []} = xref:analyse(s, locals_not_used), ?line ok = check_state(s), ?line xref:stop(s), - + ?line ok = file:delete(File1), ?line ok = file:delete(Beam1), ?line ok = file:delete(File2), ?line ok = file:delete(Beam2), ok. -misc(suite) -> - [format_error, otp_7423, otp_7831]. format_error(suite) -> []; format_error(doc) -> ["Format error messages"]; format_error(Conf) when is_list(Conf) -> ?line {ok, _Pid} = start(s), - ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]), + ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]), %% Parse error messages. - ?line 'Invalid regular expression "add(": unterminated \`(\'\n' - = fatom(xref:q(s,'"add("')), + ?line "Invalid regular expression \"add(\"" ++ _ = + fstring(xref:q(s,'"add("')), ?line 'Invalid operator foo\n' = fatom(xref:q(s,'foo E')), ?line 'Invalid wildcard variable \'_Var\' (only \'_\' is allowed)\n' @@ -2332,7 +2368,7 @@ format_error(Conf) when is_list(Conf) -> %% Other messages ?line 'Variable \'QQ\' used before set\n' = fatom(xref:q(s,"QQ")), - ?line 'Unknown constant a\n' = + ?line 'Unknown constant a\n' = fatom(xref:q(s,"{a} of E")), %% Testing xref_parser:t2s/1. @@ -2341,12 +2377,12 @@ format_error(Conf) when is_list(Conf) -> ?line 'Variable assigned more than once: E = E + E\n' = fatom(xref:q(s,"E=E + E")), ?line "Operator applied to argument(s) of different or invalid type(s): " - "E + V * V\n" = + "E + V * V\n" = flatten(xref:format_error(xref:q(s,"E + (V * V)"))), ?line {error,xref_compiler,{type_error,"(V + V) * E"}} = xref:q(s,"(V + V) * E"), ?line "Type does not match structure of constant: [m:f/3 -> g:h/17] : " - "App\n" = + "App\n" = flatten(xref:format_error(xref:q(s,"[{{m,f,3},{g,h,17}}] : App"))), ?line 'Type does not match structure of constant: [m -> f, g -> h] : Fun\n' = fatom(xref:q(s,"[{m,f},g->h] : Fun")), @@ -2360,11 +2396,11 @@ format_error(Conf) when is_list(Conf) -> xref:q(s,"condensation (# E + # V)"), ?line {error,xref_compiler,{type_error,"range (# E + # E)"}} = xref:q(s,"range (#E + #E)"), - ?line {error,xref_compiler,{type_error,"range (# E)"}} = + ?line {error,xref_compiler,{type_error,"range (# E)"}} = xref:q(s,"range #E"), % Hm... ?line {error,xref_compiler,{type_error,"E + # E"}} = xref:q(s,"E + #E + #E"), % Hm... - ?line {error,xref_compiler,{type_error,"V * E || V | V"}} = + ?line {error,xref_compiler,{type_error,"V * E || V | V"}} = xref:q(s,"V * (E || V) | V"), ?line {error,xref_compiler,{type_error,"E || (E | V)"}} = xref:q(s,"V * E || (E | V)"), @@ -2421,7 +2457,7 @@ eval(Query, E, S) -> ?format("------------------------------~n", []), ?format("Evaluating ~p~n", [Query]), ?line {Answer, NewState} = xref_base:q(S, Query, [{verbose, false}]), - {Reply, Expected} = + {Reply, Expected} = case Answer of {ok, R} when is_list(E) -> {unsetify(R), sort(E)}; @@ -2430,7 +2466,7 @@ eval(Query, E, S) -> {error, _Module, Reason} -> {element(1, Reason), E} end, - if + if Reply =:= Expected -> ?format("As expected, got ~n~p~n", [Expected]), {ok, NewState}; @@ -2442,7 +2478,7 @@ eval(Query, E, S) -> analyze(Query, E, S) -> ?format("------------------------------~n", []), ?format("Evaluating ~p~n", [Query]), - ?line {{ok, L}, NewState} = + ?line {{ok, L}, NewState} = xref_base:analyze(S, Query, [{verbose, false}]), case {unsetify(L), sort(E)} of {X,X} -> @@ -2461,7 +2497,7 @@ unsetify(S) -> %% Note: assumes S has been set up; the new state is not returned eval(Query, S) -> - ?line {{ok, Answer}, _NewState} = + ?line {{ok, Answer}, _NewState} = xref_base:q(S, Query, [{verbose, false}]), unsetify(Answer). @@ -2514,7 +2550,7 @@ check_state(S) -> functions_mode_check(S, Info) end. -%% The manual mentions some facts that should always hold. +%% The manual mentions some facts that should always hold. %% Here they are again. functions_mode_check(S, Info) -> %% F = L + X, @@ -2526,7 +2562,7 @@ functions_mode_check(S, Info) -> ?line {ok, V} = xref:q(S, "X + L + B + U"), %% X, L, B and U are disjoint. - ?line {ok, []} = + ?line {ok, []} = xref:q(S, "X * L + X * B + X * U + L * B + L * U + B * U"), %% V = UU + XU + LU, @@ -2577,11 +2613,11 @@ functions_mode_check(S, Info) -> ?line {Local, Exported} = info(Info, no_functions), ?line LX = Local+Exported, - ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM, + ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM, # (F - Extra)'), ?line true = LX =:= LXs, - ?line {LocalCalls, ExternalCalls, UnresCalls} = + ?line {LocalCalls, ExternalCalls, UnresCalls} = info(Info, no_function_calls), ?line LEU = LocalCalls + ExternalCalls + UnresCalls, ?line {ok, LEU} = xref:q(S, "# LC + # XC"), @@ -2635,7 +2671,7 @@ check_count(S) -> %% {ok, A} = xref:q(S, 'A'), {ok, M} = xref:q(S, 'AM'), - {ok, _} = xref:q(S, + {ok, _} = xref:q(S, "Extra := _:module_info/\"(0|1)\" + LM"), %% info/1: @@ -2670,7 +2706,7 @@ check_count(S) -> ok. info_module([M | Ms], S) -> - {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T", + {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T", M, S), {ok, NoFunCalls} = per_module("# (E | ~p : Mod)", M, S), {ok, NoXCalls} = per_module("# (XC | ~p : Mod)", M, S), @@ -2705,7 +2741,10 @@ f(S, A) -> flatten(io_lib:format(S, A)). fatom(R) -> - list_to_atom(flatten(xref:format_error(R))). + list_to_atom(fstring(R)). + +fstring(R) -> + flatten(xref:format_error(R)). start(Server) -> ?line case xref:start(Server) of @@ -2716,14 +2755,14 @@ start(Server) -> end. add_erts_code_path(KernelPath) -> - VersionDirs = + VersionDirs = filelib:is_dir( filename:join( [code:lib_dir(), lists:flatten( ["kernel-", - [X || - {kernel,_,X} <- + [X || + {kernel,_,X} <- application_controller:which_applications()]])])), case VersionDirs of true -> @@ -2743,5 +2782,5 @@ add_erts_code_path(KernelPath) -> [KernelPath] end end. - - + + diff --git a/lib/tools/test/xref_SUITE_data/read/read.erl b/lib/tools/test/xref_SUITE_data/read/read.erl index 4a0cc280c3..19694c9e25 100644 --- a/lib/tools/test/xref_SUITE_data/read/read.erl +++ b/lib/tools/test/xref_SUITE_data/read/read.erl @@ -106,13 +106,13 @@ funfuns() -> apply(m,f,a), % {m,f,-1} 3(a), % {'$M_EXPR','$F_EXPR',1} apply(3,[a]), % {'$M_EXPR','$F_EXPR',1} - + %% POS12=POS11+8 apply(A, A), % number of arguments is not known, {'$M_EXPR','$F_EXPR',-1} Args0 = [list], Args = [a | Args0], % number of arguments is known apply(A, Args), % {'$M_EXPR','$F_EXPR',2} - apply(m3, f3, Args), % + apply(m3, f3, Args), % NotArgs = [is_not, a | list], % number of arguments is not known apply(A, NotArgs), % {'$M_EXPR','$F_EXPR',-1} apply(m4, f4, NotArgs), % {m4,f4,-1} @@ -125,7 +125,7 @@ funfuns() -> bi() when length([]) > 17 -> foo:module_info(), module_info(), - A = tjo, + A = true andalso tjo , t:foo(A), case true of true when integer(1) -> @@ -133,7 +133,7 @@ bi() when length([]) > 17 -> false -> X = flopp end, - X == A; + self() ! X == -A orelse false; bi() -> %% POS14=POS13+18 Z = fun(Y) -> Y end, @@ -159,7 +159,7 @@ bi() -> D + E + F. %bi() -> % %% POS15=POS14+13 -% try +% try % foo:t(), % bar:t() % of @@ -169,7 +169,7 @@ bi() -> % foo:t() % catch % {'EXIT',_} -> bar:t() -% end. +% end. local() -> true. diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index faea408cc2..0c89b18065 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 2.6.5.1 +TOOLS_VSN = 2.6.6.2 |